useSyncExternalStoreのdocsを読む
これです
Refference
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
// ...
}
- The subscribe function should subscribe to the store and return a function that unsubscribes.
- The getSnapshot function should read a snapshot of the data from the store.
わかるようでわからんからソース見に行こうと思ったんやけども、どこ?
実態がつかめない
resolveDispatcherてやつがこれで
ReactCurrentDispatcherがこんなObjectなので、どっかで別のところで入れてるな
あーでもわりと実態こいつなのかもしれん
”this breaks the rules of React”が印象的、やっぱりそうよね
それっぽいのは基本ここだな。valueとgetSnapshotで新しいやつが入ってきたらre-renderしている
なんでLayoutEffectなんかはよくわからん。"This needs to be updated in the layout phase"とコメントは書いてあるけど、なんで?
ほんでその直後にuseEffectも書いてあって、たぶんこっちは単純にsucscribeをやっている。
subscribe(handleStoreChange);
を見るに、subscribe側でもcallbackを呼べばre-renderできるってことだよね?それをやるタイミングはよくわかってないけれども。
useDebugValue
なるしらんHookも使われている。なんか、loggingっぽい。
When the store changes, it should invoke the provided callback. This will cause the component to re-render.
callbackによりre-renderっておもっきし書いてあったわ
While the store has not changed, repeated calls to getSnapshot must return the same value. If the store changes and the returned value is different (as compared by Object.is), React re-renders the component.
storeの更新を検知するための機能ということがよくわかる
Returns
The current snapshot of the store which you can use in your rendering logic.
snapshotが帰ってきますよと。だからuseAtomValueみたいなんに近い。storeの更新関数はこの関数の外側にある。
The store snapshot returned by getSnapshot must be immutable.
そうですね。
Usage
いつ使いたくなるのか気になる。
毎度思うけど、この色付きの説明わかりやすすぎる。
todoStoreはこんな感じらしい。まあわかるっちゃわかる。
let nextId = 0;
let todos = [{ id: nextId++, text: 'Todo #1' }];
let listeners = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: 'Todo #' + nextId }]
emitChange();
},
subscribe(listener) {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter(l => l !== listener);
};
},
getSnapshot() {
return todos;
}
};
function emitChange() {
for (let listener of listeners) {
listener();
}
}
When possible, we recommend using built-in React state with useState and useReducer instead. The useSyncExternalStore API is mostly useful if you need to integrate with existing non-React code.
大事なことかいてあった。「使えるなら普通にReactの機能使え。既存のコードを統合するのがつらい時だけこっち」って感じね。
Another reason to add useSyncExternalStore is when you want to subscribe to some value exposed by the browser that changes over time. For example, suppose that you want your component to display whether the network connection is active. The browser exposes this information via a property called navigator.onLine.
This value can change without React’s knowledge, so you should read it with useSyncExternalStore.
なるほど、たしかにこの例は見たことがあったかもしれない。change overtime な browser API経由の値。
import { useSyncExternalStore } from 'react';
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function subscribe(callback) {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
}
なるほどですねえ
↑だとserver renderingのときに困るから、こいつを追加する
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
getServerSnapshot
が呼び出されるのは次の2点のみ
- It runs on the server when generating the HTML.
- It runs on the client during hydration, i.e. when React takes the server HTML and makes it interactive.
↑serverで適当な値返すのがアレだったら、client renderingを強制しろとのこと
Make sure that getServerSnapshot returns the same exact data on the initial client render as it returned on the server. For example, if getServerSnapshot returned some prepopulated store content on the server, you need to transfer this content to the client. One way to do this is to emit a <script> tag during server rendering that sets a global like window.MY_STORE_DATA, and read from that global on the client in getServerSnapshot. Your external store should provide instructions on how to do that.
これはあれだね、最近のDeep diveに書いてあったね。initial JSXをClientに送りつけとくやつ。
Troubleshooting
getSnapshotで新規Objectを返すと、storeが更新されなくてもsnapshot取得のたびに変わってくるのでだめ
React will re-render the component if getSnapshot return value is different from the last time.
function getSnapshot() {
// 🔴 Do not return always different objects from getSnapshot
return {
todos: myStore.todos
};
}
Your getSnapshot object should only return a different object if something has actually changed. If your store contains immutable data, you can return that data directly:
function getSnapshot() {
// ✅ You can return immutable data
return myStore.todos;
}
同様にsubscribe functionをcomponent内部で定義するのもだめ
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// 🚩 Always a different function, so React will resubscribe on every re-render
function subscribe() {
// ...
}
// ...
}
外側に定義するか、最悪useCallbackで囲っておく。
一応Playgroundにもメモった
読んでみて
利用頻度は少なそうだがReactの考え方の再確認として勉強にはなった。useOnlineStatusはふつうに使えるか。Third-pairty libraryでもおかしくないAPIだと思うけど、相当困る人がいたのか、公式見解として出す意味があったのか、どうか。
次はuseReducerとかSuspenseとか、まあまあ使用頻度の高いやつ行きたい。