ReactUse useGetSet 解析

281 阅读1分钟

介绍

react 的状态 hook,返回一个状态的 getter 函数,为了解决状态在嵌套函数的一些 bug。

const DemoWrong = () => {
  const [cnt, set] = useState(0);
  const onClick = () => {
    setTimeout(() => {
      set(cnt + 1)
    }, 1_000);
  };

  return (
    <button onClick={onClick}>Clicked: {cnt}</button>
  );
};

当按钮快速点击 cnt 不会递增。原因是因为 cnt+1 的操作是异步的,当快速点击按钮时每个任务都会在同一时间被放入队列,每个任务都会使用相同的cnt值,最终只会增加一次。 可以用useGetSet解决此类问题

import {useGetSet} from 'react-use';

const Demo = () => {
  const [get, set] = useGetSet(0);
  const onClick = () => {
    setTimeout(() => {
      set(get() + 1)
    }, 1_000);
  };

  return (
    <button onClick={onClick}>Clicked: {get()}</button>
  );
};

源码

import { Dispatch, useMemo, useRef } from 'react';
import useUpdate from './useUpdate';
import { IHookStateInitAction, IHookStateSetAction, resolveHookState } from './misc/hookState';

export default function useGetSet<S>(
  initialState: IHookStateInitAction<S>
): [get: () => S, set: Dispatch<IHookStateSetAction<S>>] {
  const state = useRef(resolveHookState(initialState));
  const update = useUpdate();

  return useMemo(
    () => [
      () => state.current as S,
      (newState: IHookStateSetAction<S>) => {
        state.current = resolveHookState(newState, state.current);
        update();
      },
    ],
    []
  );
}

源码中使用 useRef 创建一个保持不变的 ref 对象,但是 ef 对象的值发生改变之后,不会触发组件重新渲染,因此需要 useUpdate 来强制重新渲染组件。用 useMemo 返回数组,组件重新渲染不会创建新的数组。之所以使用 useMemo 是为了 useGetSet 返回的数组用于子组件传值时子组件不会重新渲染。

思考

函数式的setState来更新状态变量也能解决以上问题

const DemoCorrect = () => {
  const [cnt, set] = useState(0);
  const onClick = () => {
    setTimeout(() => {
      set(prevCnt => prevCnt + 1);
    }, 1000);
  };

  return (
    <button onClick={onClick}>Clicked: {cnt}</button>
  );
};

个人觉得函数式的setState更为简洁方便。

具体两种方式的优缺点,知道的同学能留言告知么?