日記
夜に東京で予定があったので、オフィスに出社して仕事していた。
深夜にやったツイートがなんかすごい伸びてたのでミュートにした。基本的にTweetがたくさんfavられることとかないので、初めてミュートにする機能を使った。
仕事は自作したデータ構造をReactコンポーネントのレンダリングサイクルと接続するためのAPIを書いたり、説明用のドキュメントを書いていたりしていた。
自作したMutableなオブジェクトの変更に対してReactコンポーネントを接続する時に次のようなコードをよく書く。
import {useState, useEffect} from 'react' interface Collection<T> = { updateSnapshot(value: T): void getSnapshot(): T subscribe(callback: () => void): () => void } const useCollection = <T>(observableCollection: Collection<T>): T = (observableCollection) => { const [state, updateState] = observableCollection.getSnapshot() useEffect(() => { return observableCollection.subscribe(() => { updateState(observableCollection.getSnapshot()) }) }, [observableCollection]) return state } ReactコンポーネントはupdateStateが呼び出される度にuseStateが書かれているコンポーネントを再レンダリングするという特徴を利用して、自前で定義したMutableなCollectionに変更がある度に、useCollectionを呼び出したコンポーネントが再レンダリングさせる。ということを実現している。
これにselectorやmemo化を加えた概念がReact17までのStoreライブラリとReactを繋ぐhooksの実装としてメジャーだったのだが、React18から追加されるConcurrentModeと併用した時に、画面が一瞬だけ前の状態を表示してしまうなどのチラつきが発生してしまう問題を抱えている。
See. https://youtu.be/oPfSC5bQPR8?t=245
この問題を解決するためにReact18から useSyncExternalStore というカスタムフックが提供されていてこれにstoreのメソッドを食わせることでこの問題を解決できる。
職場のコードはReact17を使っているので useSyncExternalStore を使わなくても問題ないし、ReactからこのAPIがexportされていないので使えないが、将来的にReact18に移行する際に実装を変更することなく移行できるようにしたい。
そんな人向けのライブラリとして、 https://www.npmjs.com/package/use-sync-external-store が存在する。
これは古いReactバージョンで useSyncExternalStore を使うためのshimsで、React18が動いている場合は自動でそちらにfallbackしてくれる便利ライブラリだ。