در پست قبلی دربارهی 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 سازگار ببینند؛ نه دو مقدار متفاوت. جزئیات پیادهسازی و یک دموی زنده را در همان پست قبلی نوشتهام.