ری‌اکت: واکاوی نحوه عملکرد ری‌اکت در پشت صحنه


۰


React یک کتابخانه قدرتمند جاوااسکریپت است که به سرعت تبدیل به محبوب دنیای تکنولوژی شده و نحوه ساخت رابط‌های کاربری را متحول کرده است. بعد از دو سال کار با Angular، فرصتی پیدا کردم تا React را کاوش کنم. این‌طور نیست که Angular جذاب نباشد، اما React یک جاذبه غیرقابل انکار دارد.

در این مقاله قصد دارم سازوکار درونی React را با توضیح مفاهیم کلیدی مانند DOM، virtual DOM، فرایند reconciliation و الگوریتم diffing برای شما شفاف کنم. با فهم این مفاهیم، توسعه‌دهندگان می‌توانند از قدرت React برای ساخت رابط‌های کاربری کارآمد و پاسخگو بهره ببرند. همچنین، درک درونیات React می‌تواند در رفع مشکلات عملکردی که در طول توسعه پیش می‌آیند کمک کننده باشد.

Document Object Model (DOM)

DOM یا مدل شیء سند، نمایشی از ساختار HTML است که به جاوااسکریپت اجازه می‌دهد با عناصر صفحه وب تعامل کند و آن‌ها را دستکاری کند. هر عنصر HTML یک گره (Node) در درخت DOM است که رابطه والد-فرزندی آن‌ها با توجه به تو در تو شدن در کد HTML مشخص می‌شود.
به مثال زیر توجه کنید:

1<html>  
2  <head>  
3    <title>This is title</title>  
4  </head>  
5  <body>  
6    <h1>This is a header</h1>  
7    <p> This is a paragraph</p>  
8  </body>  
9</html>

درخت DOM این کد HTML به صورت زیر خواهد بود.

درخت DOM

مرورگرهای وب از درخت DOM برای نمایش و دستکاری اسناد HTML استفاده می‌کنند که امکان شناسایی و موقعیت‌یابی آسان عناصر در صفحه مرورگر را فراهم می‌کند.

اما، تغییر دادن عناصر DOM فرایندی سنگین و زمان‌بر است. وقتی عنصری در DOM تغییر می‌کند، مرورگر باید اندازه و موقعیت آن را مجدداً حساب کند و صفحه را دوباره رندر (Repaint) کند.

اینجاست که virtual DOM وارد می‌شود. این مفهوم جایگزینی کارآمد برای به‌روزرسانی DOM واقعی در واکنش به تغییرات است و زمان لازم را به طور قابل توجهی کاهش می‌دهد. بنابراین React از Virtual DOM استفاده می‌کند.

Virtual DOM

Virtual DOM نمایشی شیء‌گونه (object) در جاوااسکریپت از DOM واقعی است. اگر به تکه کد HTML بالا نگاه کنید، می‌توانیم آن را در قالب یک شیء جاوااسکریپت شبیه به شکل زیر ببینیم.

نمایشی شیء جاوااسکریپت از DOM

React با کمک تابع React.createElement به طور دقیق JSX ( که خود نوعی شکرنگاری نحوی برای HTML است) را به این شیء جاوااسکریپت تبدیل می‌کند.

به‌روزرسانی virtual DOM بسیار سریع‌تر و بهینه‌تر از DOM واقعی است، چون نیازی به پردازش سنگین مرورگر مانند نقاشی دوباره یا تنظیم مکان ندارد و مستقیماً در همین شیء جاوااسکریپتی اعمال می‌شود.

React این فرایند را با کمک مفهومی به نام reconciliation بهبود و ساده‌تر می‌کند.

Reconciliation

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

مراحل اصلی فرایند Reconciliation:

  1. React یک Virtual DOM سبک و نسخه‌ای از DOM واقعی به صورت شیء ایجاد می‌کند.
  2. با هر تغییر در state یا props کامپوننت، React Virtual DOM جدیدی می‌سازد.
  3. React از الگوریتم diffing استفاده می‌کند که درخت‌ها را پیمایش کرده و تغییرات بین Virtual DOM جدید و قبلی را پیدا می‌کند.
  4. این مرحله مشخص می‌کند کدام کامپوننت‌ها باید در DOM واقعی رندر مجدد شوند.
  5. در نهایت، تغییرات فقط روی DOM واقعی اعمال می‌شوند.

الگوریتم diffing تضمین می‌کند که کمترین تغییرات ممکن روی DOM واقعی انجام شود.

با وجود تغییرات متعدد در state، چون فرایند Reconciliation تغییرات را بهینه می‌کند، DOM واقعی فقط یکبار به‌روزرسانی می‌شود که باعث کارایی بهتر و تجربه کاربری روان‌تر می‌شود.

امیدوارم با این گام‌ها درک خوبی از فرایند Reconciliation پیدا کرده باشید. حالا کمی دقیق‌تر به الگوریتم diffing می‌پردازیم.

Diffing Algorithm

الگوریتم diffing قلب فرایند reconciliation است که باعث سرعت و کارایی بالای آن می‌شود. این الگوریتم بسیار پیچیده است اما روی نکات کلیدی زیر تمرکز می‌کند.

1. عناصر از نوع متفاوت

وقتی که ریشه درخت Virtual DOM‌ها از نوع متفاوت باشد، الگوریتم diffing کل درخت DOM قدیمی را کنار می‌گذارد و یک درخت جدید می‌سازد. مثال زیر را ببینید:

1Original DOM:   
2
3<div>  
4  <Counter/>  
5</div>  
6
7Updated DOM:  
8
9<span>  
10  <Counter/>  
11</span>

در این مثال، ریشه از <div> به <span> تغییر کرده است. چون نوع ریشه عوض شده، الگوریتم کل درخت قدیمی را از بین می‌برد و دوباره می‌سازد. این یعنی کامپوننت <Counter> هم از نو ساخته و وضعیت (state) آن بازنشانی می‌شود.

این رفتار بر این فرض استوار است که عناصر از نوع متفاوت ساختار Virtual DOM کاملاً متمایز ایجاد می‌کنند. پس وقتی نوع ریشه تغییر کند، فرض بر این است که کل زیردرخت جایگزین شده و نیاز به بازسازی کامل دارد. این موضوع تضمین می‌کند که DOM با وضعیت برنامه همگام و سازگار باقی بماند و تجربه کاربری قابل اطمینانی ارائه دهد.

2. عناصر DOM از همان نوع

وقتی دو عنصر DOM مشابه باشند، React به ویژگی‌ها (attributes) نگاه کرده و فقط آن‌هایی که تغییر کرده‌اند را آپدیت می‌کند. مثلاً:

1Original DOM:  
2<div className="before" title="stuff" />  
3  
4Updated DOM:  
5<div className="after" title="stuff" />

با اینکه عنصر پایه همچنان <div> است، React فقط مقدار attribute className را به "after" تغییر می‌دهد و ساختار DOM را حفظ می‌کند.

3. عناصر کامپوننت از همان نوع

وقتی اجزای کامپوننت (Component elements) از نوع یکسان باشند، الگوریتم diffing از این مزیت بهره می‌برد که گره‌های DOM زیرین اجزای کامپوننت بین رندرها حفظ می‌شود.
یعنی React لازم نیست کل زیردرخت DOM را بازسازی کند بلکه صرفاً می‌تواند props و state نمونه کامپوننت موجود را به‌روزرسانی کند.
ممکن است کمی گیج‌کننده باشد، مثلاً فرض کنید:

1<Counter count={0}>  
2  <button onClick={() => setCount(count + 1)}>+</button>  
3  <button onClick={() => setCount(count - 1)}>-</button>  
4  <span>{count}</span>  
5</Counter>

یک کامپوننت Counter دارید که مقدار شمارنده را نشان می‌دهد و دکمه‌هایی برای افزایش و کاهش مقدار دارد.

اگر کاربر دکمه افزایش را بزند و مقدار به 1 تغییر کند، الگوریتم diffing درخت Virtual DOM قدیمی و جدید را مقایسه می‌کند. چون ریشه (Counter) از نوع یکسان است، الگوریتم فقط props و state کامپوننت موجود را به‌روزرسانی می‌کند و نیازی به ساختن DOM جدید از اول نیست.

4. بازگشتی روی فرزندان (Recursing on Children)

وقتی React روی فرزندان یک گره DOM پیمایش می‌کند، به صورت پیش فرض همزمان هر دو لیست فرزندان قدیم و جدید را پیمایش کرده و در صورت وجود تفاوت دستوری برای تغییر ایجاد می‌کند. مثلاً:

1Original DOM:  
2  
3<ul>  
4  <li>first</li>  
5  <li>second</li>  
6</ul>  
7  
8Updated DOM:  
9  
10<ul>  
11  <li>first</li>  
12  <li>second</li>  
13  <li>third</li>  
14</ul>

در این مثال، فرزند جدید <li>third</li> به انتهای لیست اضافه شده و الگوریتم آن را به راحتی شناسایی و اضافه می‌کند.

اما در مثال زیر دقت کنید:

1Original DOM:  
2  
3<ul>  
4  <li>first</li>  
5  <li>second</li>  
6</ul>  
7  
8Updated DOM:  
9  
10<ul>  
11  <li>zero</li>  
12  <li>first</li>  
13  <li>second</li>  
14</ul>

در این حالت، یک عنصر جدید در ابتدای لیست اضافه شده. الگوریتم diffing ابتدا <li>first</li> و <li>zero</li> را مقایسه می‌کند که متفاوتند، پس عنصر را بازسازی می‌کند. ادامه می‌دهد و <li>second</li> و <li>first</li> را هم بازسازی می‌کند. این شکل باعث می‌شود کل لیست بازسازی شود که کارآیی خوبی ندارد.

برای حل این مشکل، در React کلیدهایی (keys) معرفی شده‌اند.

1Original DOM:  
2  
3<ul>  
4  <li key="first">first</li>  
5  <li key="second">second</li>  
6</ul>  
7  
8Updated DOM:  
9  
10<ul>  
11  <li key="zero">zero</li>  
12  <li key="first">first</li>  
13  <li key="second">second</li>  
14</ul>

الگوریتم diffing حالا می‌تواند بر اساس این کلیدها مقایسه کند و فقط عناصری را که کلیدشان تغییر کرده آپدیت کند. این کار به‌روزرسانی DOM را بهینه می‌کند.

به همین دلیل است که React هشدار می‌دهد که به آیتم‌های لیست کلید (key) بدهید.

انواع الگوریتم‌های Reconciliation

در این مقاله فقط یک مرور کلی می‌آورم:

1. الگوریتم Stack reconciliation

قبل از React نسخه 16، الگوریتم diffing تغییرات را شناسایی می‌کرد و الگوریتم Stack reconciliation مسئول اعمال تغییرات بر DOM بود.
یعنی هر تغییر روی یک پشته (stack) قرار می‌گرفت و به صورت همزمان اجرا می‌شد.

مشکلات رویکرد پشته‌ای شامل موارد زیر است:

  1. فقط می‌توانست یک تغییر را در یک زمان پردازش کند که باعث کندی هنگام تغییرات متعدد می‌شد.
  2. هنگام گذار از یک حالت به حالت بعدی به ترتیب، حالت‌های میانی (مثلاً B بین A و C) در UI قابل مشاهده بود، که بازدهی را کاهش می‌داد.
  3. هنگام اجرای این به‌روزرسانی‌ها، رابط کاربری (UI) واکنش‌پذیر نبود.
  4. عملکرد انیمیشن‌ها تحت تاثیر قرار می‌گرفت.

2. الگوریتم Fiber reconciliation

از React 16 به بعد، الگوریتم Fiber به عنوان یک روش منعطف و سریع‌تر برای به‌روزرسانی DOM معرفی شد.

بهبودهای اصلی Fiber نسبت به روش قدیمی:

  1. Fiber می‌تواند چندین به‌روزرسانی را همزمان مدیریت کند و UI را همیشه پاسخگو نگه دارد.
  2. با زمان‌بندی مبتنی بر Lane، اولویت هر به‌روزرسانی مشخص می‌شود و تغییرات حیاتی سریع‌تر اعمال می‌شوند.
  3. به‌روزرسانی‌ها به قطعات کوچک‌تر شکسته می‌شوند تا با وظایف دیگر مثل تعاملات کاربر یا رندر انیمیشن‌ها همزمان اجرا شوند، مشکلی در روانی UI به وجود نیاید.

این مروری بود بر نحوه کار React در لایه‌های پایین‌تر. امیدوارم که این توضیحات به درک بهتر شما از React کمک کرده باشد. اگر سوال یا نظری داشتید حتماً بفرمایید!

جاوا اسکریپت

۰


نظرات


author
نویسنده مقاله: علی رحمانی

کد با می متعهد است که بالاترین سطح کیفی آموزش را در اختیار شما بگذارد. هدف به اشتراک گذاشتن دانش فناوری اطلاعات و توسعه نرم افزار در بالاترین سطح ممکن برای درستیابی به جامعه ای توانمند و قدرتمند است. ما باور داریم هر کسی میتواند با استمرار در یادگیری برنامه نویسی چالش های خود و جهان پیرامون خود را بر طرف کند و به موفقیت های چشم گیر برسد. با ما در این مسیر همراه باشید. کد با می اجتماع حرفه ای برنامه نویسان ایرانی.

تمام حقوق این سایت متعلق به وبسایتcodebymeمیباشد.