說明
useGlobalState:一個創建全局共享狀態的 react hook。
const useGlobalValue = createGlobalState<number>(0);
const CompA: FC = () => {
const [value, setValue] = useGlobalValue();
return <button onClick={() => setValue(value + 1)}>+</button>;
};
const CompB: FC = () => {
const [value, setValue] = useGlobalValue();
return <button onClick={() => setValue(value - 1)}>-</button>;
};
const Demo: FC = () => {
const [value] = useGlobalValue();
return (
<div>
<p>{value}</p>
<CompA />
<CompB />
</div>
);
};
源碼
// hookState.ts
export function resolveHookState<S>(nextState: IHookStateInitAction<S>): S;
export function resolveHookState<S, C extends S>(
nextState: IHookStateSetAction<S>,
currentState?: C
): S;
export function resolveHookState<S, C extends S>(
nextState: IHookStateResolvable<S>,
currentState?: C
): S;
export function resolveHookState<S, C extends S>(
nextState: IHookStateResolvable<S>,
currentState?: C
): S {
if (typeof nextState === 'function') {
return nextState.length ? (nextState as Function)(currentState) : (nextState as Function)();
}
return nextState;
}
// useEffectOnce.ts
const useEffectOnce = (effect: EffectCallback) => {
useEffect(effect, []);
};
// useIsomorphicLayoutEffect
const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
export function createGlobalState<S = any>(
initialState: IHookStateInitAction<S>
): () => [S, (state: IHookStateSetAction<S>) => void];
export function createGlobalState<S = undefined>(): () => [
S,
(state: IHookStateSetAction<S>) => void
];
export function createGlobalState<S>(initialState?: S) {
const store: {
state: S;
setState: (state: IHookStateSetAction<S>) => void;
setters: any[];
} = {
state: initialState instanceof Function ? initialState() : initialState,
setState(nextState: IHookStateSetAction<S>) {
store.state = resolveHookState(nextState, store.state);
store.setters.forEach((setter) => setter(store.state));
},
setters: [],
};
return () => {
const [globalState, stateSetter] = useState<S | undefined>(store.state);
useEffectOnce(() => () => {
store.setters = store.setters.filter((setter) => setter !== stateSetter);
});
useIsomorphicLayoutEffect(() => {
if (!store.setters.includes(stateSetter)) {
store.setters.push(stateSetter);
}
});
return [globalState, store.setState];
};
}
export default createGlobalState;
思考
函數重載
我比較喜歡這種函數重載,使用的時候很方便,使用時可以靈活傳參。
export function createGlobalState<S = any>(
initialState: IHookStateInitAction<S>
): () => [S, (state: IHookStateSetAction<S>) => void];
export function createGlobalState<S = undefined>(): () => [
S,
(state: IHookStateSetAction<S>) => void
];
export function createGlobalState<S>(initialState?: S) {}
uselayoutEffect 和 useEffect
uselayoutEffect 在組件內容渲染之前執行。
useEffect 在組件內容渲染之後執行,返回的函數在組件卸載時執行。
源碼中利用 uselayoutEffect 在組件渲染前將 stateSetter 放入 store.setters 數組中,利用 useEffect 在組件
卸載時去掉 store.setters 中的 stateSetter。
整體代碼思路解構很清晰,創建一個全局對象,當組件渲染時基於全局對象創建組件的狀態,通過執
行全局對象的 setters 隊列來更新對應的組件狀態。
總結
和 VueUse 的 useGlobalState 源碼簡單對比下:
代碼結構思路上更新喜歡 ReactUse useGlobalState 的寫法,整體實現思路很清晰,靈活度更高些。
VueUse useGlobalState 的實現上更簡單,不用單獨給組件創建和釋放狀態,而且可以創建全局狀態相關的 computed 和 watch。但 vue 組件卸載時會自動處理釋放 computed 和 watch 的 effects,所以要通過 effectScope 創建 effect 作用域避免釋放 computed 和 watch 的 effects,而這樣導致代碼結構並不是很好理解,官網對 effectScope 的解釋也很簡單。