برنتی / می‌نویسم که فکر کنم

مشکل Tearing در ری‌اکت چیست؟

1405.06.29·دنیای جاوااسکریپت

در پست قبلی درباره‌ی useSyncExternalStore نوشتم، ولی یک سؤال را باز گذاشتم: این هوک اصلاً چه مشکلی را حل می‌کند که useState یا useEffect نمی‌توانند؟ جواب یک کلمه است: تیرینگ (tearing).

تیرینگ چیست

ریک هنلون (Rick Hanlon)، از اعضای تیم ری‌اکت، در بحث رسمی «What is tearing?» در ریپازیتوری کارگروه React 18 تیرینگ را این‌طور تعریف می‌کند: زمانی که یک UI چند مقدار متفاوت برای یک state واحد نشان بدهد. توضیحش این است:

React can pause to let other work happen. Between these pauses, updates can sneak in that change the data being used to render, which can cause the UI to show two different values for the same data.

یعنی ری‌اکت می‌تواند رندر را متوقف کند تا کار مهم‌تری انجام شود، و درست در همین وقفه‌ها ممکن است داده‌ای که در حال رندر شدن است تغییر کند؛ نتیجه‌اش UI‌ای است که دو مقدار متفاوت برای یک داده نشان می‌دهد. خودش هم تأکید می‌کند این مشکل مختص ری‌اکت نیست: «this problem isn't specific to React, it's a necessary consequence of concurrency» نتیجه‌ی طبیعی هر رندرینگ concurrent است، نه یک باگ.

یک مثال ساده

let value = 0; // یک متغیر بیرون از ری‌اکت، بدون useState

function WidgetA() {
  return <span>A: {value}</span>;
}

function WidgetB() {
  return <span>B: {value}</span>;
}

فرض کنید این دو کامپوننت داخل یک رندر concurrent‌اند (مثلاً به‌خاطر یک آپدیت داخل startTransition، ری‌اکت اجازه دارد رندرشان را وسط راه رها کند و به کار مهم‌تری برسد). ری‌اکت WidgetA را رندر می‌کند، بعد رندر را متوقف می‌کند تا مثلاً به یک کلیک کاربر برسد، و در همان بازه یک event handler مقدار value را مستقیم و بیرون از state ری‌اکت از ۰ به ۱ تغییر می‌دهد. وقتی ری‌اکت رندر را ادامه می‌دهد و به WidgetB می‌رسد:

  • WidgetA قبل از تغییر رندر شده → A: 0
  • WidgetB بعد از تغییر رندر شده → B: 1

هر دو کامپوننت در نهایت در یک فریم روی صفحه می‌نشینند، ولی هرکدام یک نسخه از value را دیده‌اند. دو کامپوننتی که باید یک مقدار مشترک را نشان بدهند، در یک فریم دو عدد متفاوت نشان می‌دهند، دقیقاً همان چیزی که هنلون توضیح می‌دهد.

راه‌حل

useSyncExternalStore دقیقاً برای همین ساخته شده است: تضمین می‌کند همه‌ی خواننده‌های یک store بیرونی، در یک رندر synchronous، یک snapshot سازگار ببینند؛ نه دو مقدار متفاوت. جزئیات پیاده‌سازی و یک دموی زنده را در همان پست قبلی نوشته‌ام.

§ فهرست « دنیای جاوااسکریپت »
  1. 1405.07.18هزار آیکون در یک میلی‌ثانیه: کش کردن رسم SVG به‌جای memoکامپوننت آیکونی که SVG را از داده می‌سازد در هر رندر کمی کار اضافه می‌کند. اندازه گرفتم memo کجا کمک می‌کند و کجا نه، و با نگه داشتن عناصر رسم هر آیکون، رندر مجدد هزار آیکون را از ۳.۶ به حدود ۱ میلی‌ثانیه رساندم.
  2. 1405.07.04ساختن هوکی شبیه useSyncExternalStore از صفربا useState و useEffect قدم‌به‌قدم هوکی می‌سازم که به یک store بیرونی subscribe شود، باگ هر نسخه را نشان می‌دهم و آخر کار توضیح می‌دهم چرا نسخه‌ی خودِ ری‌اکت کاری می‌کند که از کد کاربر برنمی‌آید.
  3. 1405.06.29مشکل Tearing در ری‌اکت چیست؟تیرینگ زمانی رخ می‌دهد که یک مقدار بیرون از چرخه‌ی رندر ری‌اکت حین رندر تغییر کند و کامپوننت‌های مختلف در یک فریم، دو مقدار متفاوت از همان داده را نشان بدهند.
  4. 1405.06.29چطور در دیجیکالا با useSyncExternalStore جلوی رندرهای اضافه را گرفتیموقتی داده‌ی ویجت‌های یک لیست با تأخیر و به‌صورت پلکانی می‌رسد، نگه‌داشتن آن در یک state یا Context مشترک باعث می‌شود کل لیست با هر آپدیت دوباره رندر شود؛ useSyncExternalStore به‌همراه React.memo راه می‌دهد فقط همان ویجتی که داده‌اش عوض شده رندر شود.
  5. 1399.06.03دنیای جاوااسکریپت - مقدمهآموزش ساده و روان اصول جاوااسکریپت با مدل ذهنی Dan Abramov
  6. 1399.06.01دنیای جاوااسکریپت ۵ - Prototypesمفهوم ساده‌ی Prototypes یا نمونه اولیه در جاوااسکریپت
  7. 1399.05.31دنیای جاوااسکریپت ۴ - mutate یا تغییر دادن object"مثال‌هایی از تغییر در object
  8. 1399.05.31دنیای جاوااسکریپت ۳ - انواع روش‌های مقایسه برابریانواع روش‌های مقایسه در جاوااسکریپت Object.is
  9. 1399.05.31دنیای جاوااسکریپت ۲ - متغیرها در جاوااسکریپتمتغیرها یا Variables در جاوااسکریپت و تفاوت آنها با مقادیر یا Values
  10. 1399.05.24دنیای جاوااسکریپت ۱ - انواع مقدارها یا Valuesجاوااسکریپت شامل ۹ نوع از مقادیر هست که ۷ مورد آن از نوع Primitive و یا اولیه و اصلی و ۲ مورد دیگر که از نوع Object و Function می‌باشند.