ReactUse createGolaState 源码解析
说明
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 的解释也很简单。