یک راهنمای جامع برای قلاب useSyncExternalStore React، بررسی هدف، پیادهسازی، مزایا و موارد استفاده پیشرفته برای مدیریت وضعیت خارجی.
React useSyncExternalStore: تسلط بر همگامسازی وضعیت خارجی
useSyncExternalStore یک قلاب React است که در React 18 معرفی شده است و به شما امکان میدهد از منابع داده خارجی اشتراک بگیرید و از آنها بخوانید، به گونهای که با رندر همزمان سازگار باشد. این قلاب شکاف بین وضعیت مدیریت شده React و وضعیت خارجی را پر میکند، مانند دادههایی از کتابخانههای شخص ثالث، APIهای مرورگر یا سایر چارچوبهای UI. بیایید عمیقاً به درک هدف، پیادهسازی و مزایای آن بپردازیم.
درک نیاز به useSyncExternalStore
مدیریت وضعیت داخلی React (useState، useReducer، Context API) برای دادههای کاملاً مرتبط با درخت مؤلفه React فوقالعاده خوب عمل میکند. با این حال، بسیاری از برنامهها باید با منابع دادهای که *خارج* از کنترل React هستند، ادغام شوند. این منابع خارجی میتوانند شامل موارد زیر باشند:
- کتابخانههای مدیریت وضعیت شخص ثالث: ادغام با کتابخانههایی مانند Zustand، Jotai یا Valtio.
- APIهای مرورگر: دسترسی به دادهها از
localStorage،IndexedDB، یا Network Information API. - دادههای واکشی شده از سرورها: در حالی که کتابخانههایی مانند React Query و SWR اغلب ترجیح داده میشوند، گاهی اوقات ممکن است بخواهید کنترل مستقیمی داشته باشید.
- سایر چارچوبهای UI: در برنامههای ترکیبی که React با سایر فناوریهای UI همزیستی دارد.
خواندن و نوشتن مستقیم از این منابع خارجی در یک مؤلفه React میتواند منجر به مشکلاتی شود، به ویژه با رندر همزمان. React ممکن است مؤلفهای را با دادههای قدیمی رندر کند اگر منبع خارجی در حالی که React در حال آمادهسازی یک صفحه جدید است، تغییر کند. useSyncExternalStore این مشکل را با ارائه مکانیزمی برای React برای همگامسازی ایمن با وضعیت خارجی حل میکند.
چگونه useSyncExternalStore کار میکند
قلاب useSyncExternalStore سه آرگومان را میپذیرد:
subscribe: تابعی که یک کالبک را میپذیرد. این کالبک هر زمان که انبار خارجی تغییر کند، فراخوانی میشود. این تابع باید تابعی را برگرداند که، وقتی فراخوانی میشود، اشتراک را از انبار خارجی لغو میکند.getSnapshot: تابعی که مقدار فعلی انبار خارجی را برمیگرداند. React از این تابع برای خواندن مقدار انبار در حین رندر استفاده میکند.getServerSnapshot(اختیاری): تابعی که مقدار اولیه انبار خارجی را در سرور برمیگرداند. این فقط برای رندر سمت سرور (SSR) ضروری است. اگر ارائه نشود، React ازgetSnapshotدر سرور استفاده میکند.
قلاب مقدار فعلی انبار خارجی را برمیگرداند که از تابع getSnapshot به دست آمده است. React تضمین میکند که مؤلفه هر زمان که مقدار برگشتی توسط getSnapshot تغییر کند، همانطور که توسط مقایسه Object.is تعیین میشود، دوباره رندر میشود.
مثال پایه: همگامسازی با localStorage
بیایید یک مثال ساده ایجاد کنیم که از useSyncExternalStore برای همگامسازی یک مقدار با localStorage استفاده میکند.
Value from localStorage: {localValue}
در این مثال:
subscribe: به رویدادstorageدر شیءwindowگوش میدهد. این رویداد هر زمان کهlocalStorageتوسط یک تب یا پنجره دیگر اصلاح شود، فعال میشود.getSnapshot: مقدارmyValueرا ازlocalStorageبازیابی میکند.getServerSnapshot: یک مقدار پیشفرض برای رندر سمت سرور برمیگرداند. این میتواند از یک کوکی بازیابی شود اگر کاربر قبلاً مقداری را تنظیم کرده باشد.MyComponent: ازuseSyncExternalStoreبرای اشتراک در تغییرات درlocalStorageو نمایش مقدار فعلی استفاده میکند.
موارد استفاده و ملاحظات پیشرفته
1. ادغام با کتابخانههای مدیریت وضعیت شخص ثالث
useSyncExternalStore هنگام ادغام مؤلفههای React با کتابخانههای مدیریت وضعیت خارجی میدرخشد. بیایید به یک مثال با استفاده از Zustand نگاهی بیندازیم:
Count: {count}
در این مثال، useSyncExternalStore برای اشتراک در تغییرات در انبار Zustand استفاده میشود. توجه داشته باشید که چگونه ما useStore.subscribe و useStore.getState را مستقیماً به قلاب منتقل میکنیم و ادغام را یکپارچه میکنیم.
2. بهینهسازی عملکرد با Memoization
از آنجایی که getSnapshot در هر رندر فراخوانی میشود، اطمینان از عملکرد آن بسیار مهم است. از محاسبات گران قیمت در getSnapshot خودداری کنید. در صورت لزوم، نتیجه getSnapshot را با استفاده از useMemo یا تکنیکهای مشابه memoize کنید.
این مثال (احتمالاً مشکلساز) را در نظر بگیرید:
```javascript import { useSyncExternalStore, useMemo } from 'react'; const externalStore = { data: [...Array(10000).keys()], // Large array listeners: [], subscribe(listener) { this.listeners.push(listener); return () => { this.listeners = this.listeners.filter((l) => l !== listener); }; }, setState(newData) { this.data = newData; this.listeners.forEach((listener) => listener()); }, getState() { return this.data; }, }; function ExpensiveComponent() { const data = useSyncExternalStore( externalStore.subscribe, () => externalStore.getState().map(x => x * 2) // Expensive operation ); return (-
{data.slice(0, 10).map((item) => (
- {item} ))}
در این مثال، getSnapshot (تابع inline که به عنوان آرگومان دوم به useSyncExternalStore منتقل شده است) یک عملیات map گران قیمت را روی یک آرایه بزرگ انجام میدهد. این عملیات در *هر* رندر اجرا میشود، حتی اگر دادههای اساسی تغییر نکرده باشند. برای بهینهسازی این، میتوانیم نتیجه را memoize کنیم:
-
{data.slice(0, 10).map((item) => (
- {item} ))}
اکنون، عملیات map فقط زمانی انجام میشود که externalStore.getState() تغییر میکند. توجه: اگر انبار همان شی را تغییر دهد، یا باید externalStore.getState() را مقایسه عمیق کنید یا از یک استراتژی متفاوت استفاده کنید. این مثال برای نشان دادن ساده شده است.
3. رسیدگی به رندر همزمان
مزیت اصلی useSyncExternalStore سازگاری آن با ویژگیهای رندر همزمان React است. رندر همزمان به React اجازه میدهد تا چندین نسخه از UI را به طور همزمان آماده کند. هنگامی که انبار خارجی در حین رندر همزمان تغییر میکند، useSyncExternalStore تضمین میکند که React همیشه از بهروزترین دادهها هنگام انجام تغییرات در DOM استفاده میکند.
بدون useSyncExternalStore، مؤلفهها ممکن است با دادههای قدیمی رندر شوند، که منجر به ناسازگاریهای بصری و رفتار غیرمنتظره میشود. متد getSnapshot useSyncExternalStore به گونهای طراحی شده است که همزمان و سریع باشد و به React اجازه میدهد تا به سرعت تعیین کند که آیا انبار خارجی در حین رندر تغییر کرده است یا خیر.
4. ملاحظات رندر سمت سرور (SSR)
هنگام استفاده از useSyncExternalStore با رندر سمت سرور، ارائه تابع getServerSnapshot ضروری است. این تابع برای بازیابی مقدار اولیه انبار خارجی در سرور استفاده میشود. بدون آن، React سعی میکند از getSnapshot در سرور استفاده کند، که ممکن است در صورتی که انبار خارجی به APIهای خاص مرورگر (به عنوان مثال، localStorage) متکی باشد، امکانپذیر نباشد.
تابع getServerSnapshot باید یک مقدار پیشفرض برگرداند یا دادهها را از یک منبع سمت سرور (به عنوان مثال، کوکیها، پایگاه داده) بازیابی کند. این تضمین میکند که HTML اولیه رندر شده در سرور حاوی دادههای صحیح است.
5. رسیدگی به خطا
رسیدگی به خطای قوی، به ویژه هنگام برخورد با منابع داده خارجی، بسیار مهم است. توابع getSnapshot و getServerSnapshot را در بلوکهای try...catch قرار دهید تا خطاهای احتمالی را مدیریت کنید. خطاها را به درستی ثبت کنید و مقادیر پشتیبان را ارائه دهید تا از خرابی برنامه جلوگیری شود.
6. قلابهای سفارشی برای قابلیت استفاده مجدد
برای ارتقای قابلیت استفاده مجدد کد، منطق useSyncExternalStore را در یک قلاب سفارشی محصور کنید. این باعث میشود که اشتراکگذاری منطق در چندین مؤلفه آسانتر شود.
به عنوان مثال، بیایید یک قلاب سفارشی برای دسترسی به یک کلید خاص در localStorage ایجاد کنیم:
اکنون، میتوانید به راحتی از این قلاب در هر مؤلفهای استفاده کنید:
```javascript import useLocalStorage from './useLocalStorage'; function MyComponent() { const [name, setName] = useLocalStorage('userName', 'Guest'); return (Hello, {name}!
setName(e.target.value)} />بهترین روشها
- سریع نگه داشتن
getSnapshot: از محاسبات گران قیمت در تابعgetSnapshotخودداری کنید. در صورت لزوم نتیجه را memoize کنید. getServerSnapshotرا برای SSR ارائه دهید: اطمینان حاصل کنید که HTML اولیه رندر شده در سرور حاوی دادههای صحیح است.- از قلابهای سفارشی استفاده کنید: منطق
useSyncExternalStoreرا در قلابهای سفارشی برای قابلیت استفاده مجدد و قابلیت نگهداری بهتر محصور کنید. - به درستی خطاها را مدیریت کنید:
getSnapshotوgetServerSnapshotرا در بلوکهایtry...catchقرار دهید. - اشتراکها را به حداقل برسانید: فقط به بخشهایی از انبار خارجی که مؤلفه واقعاً به آنها نیاز دارد، مشترک شوید. این باعث کاهش دوباره رندرهای غیرضروری میشود.
- جایگزینها را در نظر بگیرید: ارزیابی کنید که آیا
useSyncExternalStoreواقعاً ضروری است یا خیر. برای موارد ساده، سایر تکنیکهای مدیریت وضعیت ممکن است مناسبتر باشند.
جایگزینهایی برای useSyncExternalStore
در حالی که useSyncExternalStore یک ابزار قدرتمند است، اما همیشه بهترین راهحل نیست. این جایگزینها را در نظر بگیرید:
- مدیریت وضعیت داخلی (
useState،useReducer، Context API): اگر دادهها کاملاً با درخت مؤلفه React مرتبط هستند، این گزینههای داخلی اغلب کافی هستند. - React Query/SWR: برای واکشی دادهها، این کتابخانهها قابلیتهای عالی برای کش کردن، بیاعتبار کردن و مدیریت خطا ارائه میدهند.
- Zustand/Jotai/Valtio: این کتابخانههای مدیریت وضعیت مینیمالیستی، راهی ساده و کارآمد برای مدیریت وضعیت برنامه ارائه میدهند.
- Redux/MobX: برای برنامههای پیچیده با وضعیت جهانی، Redux یا MobX ممکن است انتخاب بهتری باشد (اگرچه آنها boilerplate بیشتری را معرفی میکنند).
انتخاب به الزامات خاص برنامه شما بستگی دارد.
نتیجه
useSyncExternalStore یک افزودنی با ارزش به ابزار React است که ادغام یکپارچه با منابع وضعیت خارجی را در عین حفظ سازگاری با رندر همزمان امکانپذیر میکند. با درک هدف، پیادهسازی و موارد استفاده پیشرفته آن، میتوانید از این قلاب برای ساخت برنامههای React قوی و با عملکردی استفاده کنید که به طور مؤثر با دادهها از منابع مختلف تعامل دارند.
به یاد داشته باشید که عملکرد را در اولویت قرار دهید، خطاها را به درستی مدیریت کنید و قبل از رسیدن به useSyncExternalStore، راهحلهای جایگزین را در نظر بگیرید. با برنامهریزی و پیادهسازی دقیق، این قلاب میتواند انعطافپذیری و قدرت برنامههای React شما را به میزان قابل توجهی افزایش دهد.
کاوش بیشتر
- اسناد React برای useSyncExternalStore
- نمونههایی با کتابخانههای مختلف مدیریت وضعیت (Zustand، Jotai، Valtio)
- معیارهای عملکرد مقایسه
useSyncExternalStoreبا رویکردهای دیگر