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 的解釋也很簡單。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章