介绍
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更为简洁方便。
具体两种方式的优缺点,知道的同学能留言告知么?