因為畫圖的需要,做了一個簡單的等比例計算機。
這篇除了展示成果之外也會就使用的 Jotai 的心得做一些簡單的討論,也包含和 zustand 和 React context 的比較。
Aspect Ratio Calculator
Zustand
在這之前我個人比較常使用的 state management 是 zustand 。當初先嘗試 zustand 的動機,其實是來自一封 Buildable.dev 的 Daniel Spataro 的信,裡面介紹了他們家的產品,另外也推薦我嘗試使用 zustand 。研究了一下文件之後覺得 zustand 的 API 非常簡單, bundle size 也非常小,並且可以輕易地和 React tree 之外的東西互動,例如:我們之前就有在 axios instance 的 interceptors 裡面更新 request token ,而這個 token 正好就能使用 zustand/vallina 來管理,使 react 外的事件或資料可以和 react components 互動。主要是在 refresh token 失敗的時候會將該 store 清空,觸發 react component 顯示已登出的提示。
Jotai
一開始是想找 zustand memo computed 的方法,後來找到這篇: Using getter to calculate computed values #132 ,決定嘗試使用 Jotai 。
比較
建議可以先看 dai-shi 寫的比較。
兩者的設計方向完全不一樣,解決不一樣的問題。我個人是認為視情況兩個都使用也沒什麼問題。我主要會提一些在上面文章以外的想法。
先從我一開始的需求開始說起。
在 zustand 中如果要 memo computed value ,一般會這樣做:
/**
* Memoizing selectors
* https://github.com/pmndrs/zustand#memoizing-selectors
*/
const selector: Parameters<typeof useStore>[0] = ({ myValue }) => ({ myValue });
function MyComponent() {
const { myValue } = useStore(selector);
const computedValue = useMemo(() => heavyComputeFn(myValue), [myValue]);
/* ... */
}
像這樣的情況如果 MyComponent
被拿來一次渲染多個 Node 或是有其他 component 使用同樣的 heavyComputeFn()
,相同的計算就會被重複執行;
Jotai 的方式是:
const derivedAtom = atom((get) => heavyComputeFn(get(myAtom)));
function MyComponent() {
const computedValue = useAtomValue(derivedAtom);
/* ... */
}
相同的情況下 heavyComputeFn()
就只會執行一次。
除了解決上面的需求外, Jotai 還有以下優點:
- Jotai 不需要做 zustand Memoizing selectors 的優化。
- Jotai atom 可以自訂 update function ,通常會設計得有點像 reducer ,這個部分雖然我比較喜歡 zustand 的寫法,但一般的使用情況下 zustand 只能使用同一個 store 內的資料,而 jotai 可以自由地依賴其他 atom。個人十分建議把邏輯可能會被共用的部分都使用 atom 來處理,沒用到的時候都不會被執行,執行的時候最多也只有一次,而因為 atom 都是同一個 instance 所以 atom props 的傳遞也不會造成 re-render 。
兩者的 Provider 都無法取代 Context Provider ,他們的 props 都是 initial value 而不能 always computed 。 在 Aspect Ratio Calculator 內我將 atom 全部都丟進 Context Provider 中,雖然在這個範例中沒有明顯的效果,但如果相同 computed atom value 如果在多個 component 被重複使用一樣只會計算一次。我認為這樣使用是沒什麼大問題的,但要記得來自 Provider Props 的值必須在更新的時候也同步到對應的 atom ,而且時間上也晚一個 tick 。如果不是高度重複 heavy compute functon 真的需要減少呼叫次數的極端複雜的情況建議還是使用一般的 Context Provider 即可。可以參考 Can Jotai be used in order to replace the React Context API? #973 。
個人總結
- Global / page context: jotai 和 zustand 都不錯,個人認為 jotai 更勝一籌,主要是 derivedAtom 的部分可以避免重複計算。
- 需要和 React tree 之外的事件或資料互動的狀態: 直接 zustand 。
- Local Context / Nested Context : 目前個人覺得還是 React Context + Provider 比較容易維護, 真的有必要減少重複相同計算的話 再考慮在 provider 內塞 atom 。
我認為沒有哪個比較好,都各自有適合他們的地方。用!都用爆吧!