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 的解释也很简单。

posted @ 2023-05-21 22:18  余子酱  阅读(44)  评论(0编辑  收藏  举报