تفاوت بین Virtual DOM و DOM

صفحاتی که در حال حاظر با Reactjs پیاده سازی می‌شوند با اصطلاحی با عنوان Virtual Dom بروزرسانی و تغییر می‌کنند.

تفاوت بین Virtual DOM و DOM

اما دقیقا “Virtual DOM” به چه معناست؟

DOM

برای واضح‌تر شدن موضوع، DOM مخفف Document Object Model و یک متن مفهمومی ساختار یافته است که برای توسعه دهندگان این متن همان کد HTML تلقی میشه و در نهایت تگ‌های HTML به گره‌های(Nodes) یک DOM تبدیل میشن.

تا به اینجا متوجه می‌شیم که HTML یک متن معمولیه که DOM در حافظه‌ای اون رو به شکل خاصی ارائه میده. همچنین HTML DOM یک رابط(API) در اختیار توسعه دهنده میگذاره که به کمکش میشه گره‌ها رو دست‌کاری کرد که متدهایی مثل getElementById و removeChild رو درون خودش داره. معمولا برای انجام اموری که با DOM سر و کار داره از جاوااسکریپت استفاده می‌کنیم چرا؟ … خوب، هیچکس نمیدونه چرا :)

بنابراین، هر موقعه بخواهیم محتوا رو به صورت پویا دستکاری کنیم باید گره‌های DOM رو به شکل زیر دستکاری کنیم:

1
2
var item = document.getElementById("content");
item.parentNode.removeChild(item);

در اینجا شی document به عنوان ریشه گره‌ها تلقی میشه همچنینparentNode و removeChild متدهایی هستند که HTML DOM API در اختیار ما قرار میده.

مشکلات DOM

از آنجایی که ساختار درختی HTML DOM از ساختار سند HTML پیروی میکنه نکته مثبت و خوبیه که باعث میشه که به راحتی بین گره‌های DOM جابه‌جا شد و دسترسی داشت.

این روزها DOMها سرشون خیلی شلوغ :) و از طرفی هر چقدر که به سمت وب اپلیکیشن‌های پویا(SPAs) حرکت می‌کنیم، بیشتر نیاز داریم که درخت DOM به صورت سریع و متعدد مورد تغییر و بروزرسانی قرار بگیره.

یک DOM رو در نظر بگیرید که ۱۰۰۰ تگ div ساخته و به خاطر داشته باشید که توی عصر مدرن وب هستیم و اپلیکشین ما از نوع SPA هست و متدهای زیادی داریم که میتونیم رویدادها، کلیک‌ها و … رو کنترل کنیم، در حالت معمول توی jQuery به شکل زیر عمل میشه:

  • پیدا کردن هر گره مورد نظری که رویداد به اون مرتبطه
  • بروزرسانی کردن گره در صورت لزوم

در این صورت دو مشکل وجود داره:

  1. مدیریت دشوار: تصور کنید قراره یک کنترل کننده رویداد(Event Handler) رو تنظیم کنید. اگر رشته و اساس کار از دستتون بره باید به صورت عمیق درون کدها غوطه‌ور بشید تا بفهمید قضیه از چه قراره که انجام این کار ریسک به همراه داره و وقت گیره.

  2. ناکارآمد بودن: واقعا نیازه برای پیدا کردن به صورت دستی این همه کار انجام بدیم؟ اگر مقداری هوش به خرج بدیم شاید از پیش بتونیم بگیم کدام گره‌ها بروز شدند.

اینجاست که React به کمک ما میاد. راه حل مشکل اول به اعلان کردن برمیگرده. به جای تکنیک‌های سطح پایین مثل جابه‌جا شدن دستی بین درخت DOM کافیه اعلان کنیم که یک Component قراره به چه شکل باشه. React کار های سطح پایین رو انجام میده و به کمک رابط HTML DOM متدها فراخوانی میشن. React تمایل داره شما نگران نباشید و در آخر Component شبیه اون چیزی میشه که باید باشه.

اما این مشکل کارایی رو حل نمیکنه و دقیقا الان زمانیه که Virtual DOM به حل این مشکل کمک میکنه.

Virtual DOM

اول از همه باید گفت که Virtual DOM توسط React ساخته نشده اما React از اون استفاده می‌کنه.

Virtual DOM سبکه و جدا از مفاهیم مرورگرها عمل میکنه و از آن جایی که خود DOM الهام گرفته شده هست، Virtual DOM هم به همین شکله در حقیقت الهامی از الهام دیگه.

تفاوت بین Virtual DOM و DOM

شاید بهتره فکر کنیم که Virtual DOM به عنوان یک نسخه کپی شده از HTML DOM درون React به شکل محلی و ساده شده عمل میکنه و به React اجازه میده تا محاسبات توی دنیای الهام شده خودش انجام بشه و بعد از کارهای DOM واقعی عبور کنه.

تفاوت آنچنانی مابین DOM واقعی و DOM مجازی وجود نداره و به همین دلیل در قطعه کد زیر می‌بینیم که چقدر JSX در React شبیه به HTML معمولیه:

1
2
3
4
5
6
7
8
9
class HelloWorld extends React.Component {
render() {
return(
<div className="HelloWorld">
Hello Buddy, I'm your new component!.
</div>
);
}
}


در بیشتر حالت‌ها، زمانی که شما یک کد HTML دارید و میخواید به یک کامپوننت ایستا React تبدیل کنید نیاز به انجام مراحل زیر دارید:

  • نوشتن کد HTML درون تابع render
  • اگر میخواید به تگ‌ها خاصیت class بدید حتما اون رو با className جایگزین کنید اگر کنجکاوید که چرا همون class نیست باید بگم عنوان class توی Javascript از پیش رزرو شده و امکان استفاده رو به ما نمیده و JSX هم که برپایه Javascript هست.

تفاوت‌هایی تقریبا زیادی بین DOMها وجود داره اما جزئی:

  • سه خاصیت از Virtual DOM توی DOM واقعی وجود نداره: key ،ref و dangerouslySetInnerHTML. اطلاعات بیشتر.
  • React محدودیت‌ها و توضیحات بیشتری رو در این لینک اشاره کرده.

ReactElement در مقابل ReactComponent

زمانی که در مورد Virtual DOM صحبت می‌کنیم مهمه که به تفاوت این دو هم بپردازیم.

ReactElement

ReactElement در حقیقت یک نوع اصلی از React هست. توضیحات مستندات React:

ReactElement نمایش مجازی یک Element از DOM است همچنین سبک،‌ بدون وضعیت، تغییر ناپذیر است.

ReactElements درون Virtual DOM زندگی می‌کنه. به ما کمک می‌کنه که گره‌ها رو بسازیم. خاصیت تغییر ناپذیری(Immutability) باعث میشه راحت‌تر و سریع‌تر مقایسه و بروزرسانی کرد. این یکی از دلایل عملکرد عالی React هست.

یک ReactElement شامل چه چیزهایی می‌تونه باشه؟ تقریبا همه‌ی تگ‌های HTML، اگر تمایل دارید میتونید لیست کامل این تگ‌ها رو ببینید.

پس از تعریف، ReactElements می‌تونه در DOM واقعی رندر(Render) بشه. در این لحظه شاهد حضور React هستیم که Elementها رو کنترل و مدیریت می‌کنه و در آخر به گره‌های کند و خسته کننده DOM تبدیل میشن:

1
2
3
4
var root = React.createElement('div');
ReactDOM.render(root, document.getElementById('example'));
// اگر از این متعجب هستید که چرا تابع `render` متعلق به پکیج `ReactDOM`هست
// به پی‌نوشت مطلب مراجعه کنید.


برای مثال بالا روشی دیگه‌ای وجود داره که به کمک JSX می‌تونید این کار رو انجام بدید:

1
2
var root = <div />;
ReactDOM.render(root, document.getElementById('example'));


یکبار دیگه باید تاکید کرد که ReactElements از اجزاء اصلی React Virtual DOM تلقی میشه، از آنجایی که تغییر ناپذیر هستند به نظر خیلی برای ما برنامه نویس‌ها کاربردی نیست. به نظر بیشتر ترجیح داده میشه با قطعه کد‌هایی شبیه HTML سر و کار داشته باشیم به همراه متغیرها، ثابت‌ها و … اینطور نیست؟ در زیر به این موضوع می‌پردازیم …

ReactComponent

آنچه که ReactComponent رو از ReactElement متفاوت می‌کنه به وضعیت پذیری ReactComponents برمیگرده.
به طور معمول برای ساخت یک Component از متد زیر استفاده می‌کنیم:

1
2
3
4
5
6
7
8
9
class HelloWorld extends React.Component {
render() {
return(
<div className="HelloWorld">
Hello Buddy, I'm your new component!.
</div>
);
}
}


می‌بینیم که قطعه کد HTML ما از متد render بازگشت داده شده یعنی وضعیتی رو به خودش اختصاص داده و این بهترین چیزیه که React به ما میده چرا که هرگاه وضعیت تغییر کنه Component ما هم مجددا رندر میشه. اگر مثال زیر رو به خوبی بررسی کنید با مفهوم rerender بیشتر آشنا خواهید شد.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = {
secondsElapsed: 0
};
this.tick = this.tick.bind(this);
}
tick() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
}
componentDidMount() {
this.interval = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
}


قطعا ReactComponents قابلیت این رو داره که یک ابزار عالی برای ساخت Componentهای پویا HTML باشه. هرچند به VirtualDOM دسترسی ندارند اما به شکل راحتی می‌تونن به ReactElements تبدیل بشن:

1
2
var element = React.createElement(MyComponent);
var element = <MyComponent />;

چه چیزی باعث تفاوته؟

ReactComponents عالیه، می‌تونیم کامپوننت های فراوانی داشته باشیم و به راحتی مدیریت کنیم. اما خوب این قدرت رو نداره که به Virtual DOM دسترسی داشته باشه و احتمالا ما به این مورد نیاز داریم که فراتر از اون چیزی که می‌خوایم عمل کنیم.

هر زمانی که وضعیت ReactComponent تغییر کنه احتمالا تغییراتی درون DOM اصلی هم رخ میده که React برای حل این موضوع ReactComponent رو به ReactElement تبدیل می‌کنه و بعد ReactElement رو به Virtual DOM به سرعت و راحت اضافه، مقایسه و بروز کنه. چگونگی انجام شدن این فرآیند به الگوریتم diff برمی‌گرده. مهم‌ترین نکته این موضوع انجام شدن سریع‌تر فرآیند‌ها بالا نسبت به DOM واقعی هست.

زمانی که React از وجود diff مطلع میشه، اون رو به کد سطح پایین(HTML Code) تبدیل کرده که یعنی در DOM واقعی هم رندر و اجرا شده، این کد بر حسب هر مرورگر بهینه میشه.

خلاصه

Virtual DOM واقعا ویژگی هست که باید در صفحات وب استفاده کرد؟ من هم موافقم. در عمل، React عملکرد بسیار خوبی داره و همچنین Virtual DOM قطعا می‌تونه کمک کننده باشه.

پی نوشت. اگر متوجه نشدید باید اشاره کنم که فیسبوک از نسخه 0.14 قسمت مرتبط به DOM رو از کتابخانه اصلی React جدا کرد و کتابخانه مجزایی با عنوان react-dom انتشار داد که اطلاعات بیشتر رو می‌تونید اینجا مطالع کنید.