alibaba 开源的 ahooks 钩子

官方文档
代码仓库地址

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

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

useRef => useLatest

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

1
2
3
4
5
const refCount= useRef(0)
refCount.current=count

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

useEvent => useMemoizedFn

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

1
2
3
4
const [data, setData] = useState(0)
const callBack = useCallback(()=>{
console.log(data)
}, [])

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

1
2
3
4
5
6
7
8
const [data, setData] = useState(0)

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

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

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

1
2
3
4
5
6
7
8
9
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 的闭包问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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 高,细节做的好