alibaba 开源的 ahooks 钩子
ahooks 常用钩子
2022/6/5 10:03:00
➡️

官方文档 代码仓库地址

ahooks 是怎么解决 React 的闭包问题的

react 函数式组件闭包导致的问题:

useRef => useLatest

在点击 click me 按钮后 count 无论如何更新,useEffect 回调内 count 获取的始终是 0

function Counter() {
  const [count, setCount] = useState(0);

  // useEffect 内延时获取的的 count 是一个闭包,要判断闭包的 count 所处于环境也就是
  //useEffect 回调执行的
  // 时候 count 的值,为了不频繁的创建及销毁定时器,给 useEffect 的依赖是一个空数组
  // 正常是在 useEffect 回调内会进行业务逻辑的处理
  useEffect(() => {
    const id = setInterval(() => {
      document.getElementById("p1")!.innerText = `you delay get count: ${count}`;
    }, 2000);
    return () => {
      clearInterval(id);
    };
  }, []);

  return (
    <div>
      <div> you click {count} times</div>
      <div id="p1"></div>
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  );
}

利用 react 自带的 useRef 解决

function Counter() {
  const [count, setCount] = useState(0);

  //refCount 组件装载后的后续render 后不会再次进行定义,具有唯一性
  //可以通过其把最新的值挂载在 useRef 上
  const refCount = useRef(0);
  refCount.current = count;

  // useEffect 内延时获取的的 count 是一个闭包,要判断闭包的 count 所处于环境也就是
  // useEffect 回调执行的
  // 时候 count 的值,为了不频繁的创建及销毁定时器,给 useEffect 的依赖是一个空数组
  // 正常是在 useEffect 回调内会进行业务逻辑的处理
  useEffect(() => {
    const id = setInterval(() => {
      document.getElementById(
        "p1"
      )!.innerText = `you delay get count: ${refCount.current}`;
    }, 2000);
    return () => {
      clearInterval(id);
    };
  }, []);

  return (
    <div>
      <div> you click {count} times</div>
      <div id="p1"></div>
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  );
}

ahooks 封装的钩子 useLatest

const refCount = useRef(0);
refCount.current = count;

//ahooks useLatest  useLatest 源码也是用上面的俩行代码
const refCount = useLatest(count);

useEvent => useMemoizedFn

回掉函数需要缓存,但是 data 形成了闭包 如果把 data 作为依赖项,又会造成缓存失效

const [data, setData] = useState(0);
const callBack = useCallback(() => {
  console.log(data);
}, []);

利用 react 自带的 useRef 保持状态数据为最新 如果 useCallback 回调函数里有许多状态数据需要引用,要写好多无用的代码

const [data, setData] = useState(0);

const refData = useRef(data);
refData.current = data;

const callBack = useCallback(() => {
  console.log(refData.current);
}, []);

利用 react 自带的 useRef 保持函数为最新,不需要考虑状态数据的问题

const [data, setData] = useState(0);
const fun = () => {
  console.log(data);
};
const refData = useRef(fun);
refData.current = fun;
const callBack = useCallback(() => {
  refData.current();
}, []);

react 一个新的提案,引入 useEffect 钩子 此函数专门为解决 useCallback 的闭包问题

function useEvent(handler) {
  const handlerRef = useRef(null);

  // 在组件render 后更新`handlerRef.current`指向
  // 这样可以确保 handler 内获取的状态数据都是最新的
  useLayoutEffect(() => {
    handlerRef.current = handler;
  });

  // 用useCallback包裹,使得render时返回的函数引用一致
  return useCallback((...args) => {
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

ahooks 的 useMemoizedFn

function useMemoizedFn<T extends noop>(fn: T) {
  // 通过 useRef 保持其引用地址不变,并且值能够保持值最新
  const fnRef = useRef<T>(fn);
  fnRef.current = useMemo(() => fn, [fn]);
  // 通过 useRef 保持其引用地址不变,并且值能够保持值最新
  const memoizedFn = useRef<PickFunction<T>>();
  if (!memoizedFn.current) {
    // 返回的持久化函数,调用该函数的时候,调用原始的函数
    memoizedFn.current = function (this, ...args) {
      return fnRef.current.apply(this, args);
    };
  }

  return memoizedFn.current as T;
}

react-use

useRequest 网络请求 hooks

网络请求一直是前端应用最为核心的部分之一。从 jQuery 对 ajax 的封装开始到 axios,请求库这几年已经得到了快速的发展。
尤其是随着 hooks 的出现,请求库终于进入了一个新的时代。

在传统的请求模型里,一个请求的完整流程是这样的:

  1. 用户点击,触发请求流程
  2. 设置相关视图的状态为 loading
  3. 发起请求
  4. 处理响应结果,关闭视图的 loading 状态

最热门的 useRequest、swr 和 react-query 三个请求 hooks

SWR 官方文档 react-query

useRequest 参考 SWR 的功能 和 Antd 有更好的配合,react-query 细节控制更好

个人更倾向于 react-query,因为 starts 高,细节做的好

👍🎉🎊