useSyncExternalStore Hook
useSyncExternalStore Hook — React-ի կապը արտաքին store-երի հետ
React 18-ում ավելացավ նոր hook՝ useSyncExternalStore, որը նախատեսված է React-ին անվտանգ և կայուն ձևով միացնելու համար ցանկացած արտաքին store կամ տվյալների աղբյուրին։ Այն ապահովում է, որ React կոմպոնենտը համաժամեցվի տվյալների վերջին արժեքի հետ՝ առանց race condition-ների։
Արդյունքում՝ React-ը միշտ render է անում consistent տվյալներով, նույնիսկ եթե store-ը փոխվել է render-ի ընթացքում։ Սա այն hook-ն է, որի վրա կառուցված են ժամանակակից գրադարանները՝ Redux, Zustand, jotai, Recoil և այլն։
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
Ստեղծենք փոքր JavaScript store, որը պահում է արժեք և թույլ է տալիս subscribe անել փոփոխություններին։
// store.js
let count = 0;
let listeners = new Set();
export const store = {
getSnapshot() {
return count;
},
subscribe(callback) {
listeners.add(callback);
return () => listeners.delete(callback);
},
increment() {
count++;
listeners.forEach((cb) => cb());
}
};
Այստեղ մենք ունենք պարզ counter store, որը կարող է․ ✅ պահել արժեքը (`count`) ✅ տեղեկացնել բոլոր subscriber-ներին, երբ count-ը փոխվում է
// Counter.js
import React from 'react';
import { store } from './store';
import { useSyncExternalStore } from 'react';
export default function Counter() {
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={store.increment}>+1</button>
</div>
);
}
Այս դեպքում React-ը միշտ ստանում է վերջին արժեքը `store`-ից և rerender է անում միայն այն ժամանակ, երբ store-ը իրոք փոփոխվում է։ Սա շատ ավելի արդյունավետ և ապահով է, քան օգտագործել `useEffect` կամ `useState` store-ի սինխրոնացման համար։
Նախկինում շատ գրադարաններ (օր. Redux-ի հին տարբերակներ) օգտագործում էին useEffect՝ store-ից տվյալներ վերցնելու և React-ին տեղեկացնելու համար։ Սակայն concurrent rendering-ի ժամանակ դա առաջացնում էր race condition՝ երբ React-ը render էր անում հին արժեքներով։
useSyncExternalStore-ը լուծում է այդ խնդիրը՝ երաշխավորելով, որ render-ի ընթացքում React-ը միշտ կօգտագործի վերջին consistent snapshot-ը։
Օրինակ՝ ունես websocket, որը ստանում է real-time տվյալներ։ useSyncExternalStore-ը կարող է օգտագործվել, որպեսզի React կոմպոնենտը ստանա վերջին արժեքները՝ առանց rerender lag-ի։
import React, { useSyncExternalStore } from 'react';
function createSocketStore() {
let data = 'waiting...';
let listeners = new Set();
const ws = new WebSocket('wss://example.com/socket');
ws.onmessage = (e) => {
data = e.data;
listeners.forEach((cb) => cb());
};
return {
subscribe(cb) {
listeners.add(cb);
return () => listeners.delete(cb);
},
getSnapshot() {
return data;
}
};
}
const socketStore = createSocketStore();
export default function LiveData() {
const value = useSyncExternalStore(
socketStore.subscribe,
socketStore.getSnapshot
);
return <div>📡 Վերջին տվյալը՝ {value}</div>;
}
Այստեղ React-ը ստանում է live տվյալներ socket-ից, բայց միշտ consistent — առանց վտանգի, որ render-ը կհասնի հին արժեքին։
Եթե SSR նախագծում (օր. Next.js) օգտագործում ես store, կարող ես տալ server-side snapshot — օրինակ default արժեքը։
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot,
() => 0 // Server-side default
);
Այսպես React-ը չի գանգատվի, որ server և client snapshot-ները տարբեր են hydration-ի ժամանակ։
Գրիր փոքր store, որը․
Փորձիր նույնը անել useEffect/useState տարբերակով և տես տարբերությունը՝ render consistency-ի և rerender արագության մեջ։