یک راهنمای جامع برای قلاب 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
با رویکردهای دیگر