Zustand 是一個輕量級、快速且具擴展性的狀態管理解決方案,採用簡化的 Flux 原則,並基於發布/訂閱模式和 React hooks。
Zustand 簡化了 Flux 架構,主要是使用 store 來決定如何更改 state。一旦 store 傳回新狀態,UI 將使用更新的更改進行渲染。因此,Zustand 不會有 Flux 那樣的 Action 和 Dispatcher。除此之外,Zustand 的 store 是獨立於 React 的,即使是在非 React 的程式碼中也能夠使用。
import { create } from "zustand";
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
使用 create
函數建立一個 store,set
參數去改變你 state 中的狀態。
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return <h1>{bears} around here ...</h1>;
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation);
return <button onClick={increasePopulation}>one up</button>;
}
跟 redux toolkit 相比,zustand 程式碼算相對好讀很多。
const createStoreImpl = (createState) => {
let state; //用來存放全域的 state
const listeners = new Set(); // 存放監聽器的集合
const setState = (partial, replace) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
//如果新狀態與舊狀態不同,則會更新狀態並通知所有訂閱的監聽器。
if (nextState !== state) {
const previousState = state;
state =
replace || typeof nextState !== "object" || nextState === null
? nextState
: Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
//subscribe 用來訂閱狀態變更,當狀態變更時,listener會被通知。
const subscribe = (listener) => {
listeners.add(listener);
//當取消訂閱時,刪除監聽器
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = (state = createState(setState, getState, api));
return api;
};
const createStore = (createState) => createStoreImpl(createState);
export function useStore(api, selector = identity) {
//useSyncExternalStore 是 React 18 的 Hook,詳細說明可參考下方內容。
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
);
React.useDebugValue(slice); //在 React DevTools 中顯示 Hook 的值
return slice;
}
const createImpl = (createState) => {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
useSyncExternalStore 是 React 18 的 Hook ,用於訂閱外部狀態,並在狀態變更時重新渲染組件。
使用上會長這樣:
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
import * as React from "react";
const { useState, useEffect, useLayoutEffect, useDebugValue } = React;
export function useSyncExternalStore(subscribe, getSnapshot) {
// 取得當前快照
const value = getSnapshot();
const [{ inst }, forceUpdate] = useState({
inst: { value, getSnapshot },
});
//在瀏覽器重繪 (repaints) 前執行,同步檢查快照的改變,並根據情況強制更新
useLayoutEffect(() => {
inst.value = value;
inst.getSnapshot = getSnapshot;
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
}, [subscribe, value, getSnapshot]);
//用來訂閱外部狀態的變更,當外部狀態改變時,會檢查新狀態是否和舊狀態不同,並強制重新渲染組件
useEffect(() => {
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
const handleStoreChange = () => {
if (checkIfSnapshotChanged(inst)) {
forceUpdate({ inst });
}
};
return subscribe(handleStoreChange);
}, [subscribe]);
useDebugValue(value);
return value;
}
function checkIfSnapshotChanged(inst) {
const latestGetSnapshot = inst.getSnapshot;
const prevValue = inst.value;
try {
const nextValue = latestGetSnapshot();
return prevValue !== nextValue;
} catch (error) {
return true;
}
}
參考資料:
https://github.com/pmndrs/zustand
https://juejin.cn/post/7178318352174022717
https://jimhuang.dev/react/zustand-source-code/
https://github.com/pmndrs/zustand/blob/main/src/index.ts
https://github.com/pmndrs/zustand/blob/main/src/vanilla.ts
https://react.dev/reference/react/useSyncExternalStore
https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js