useCallback闭包与setState更新矛盾
useCallback的函数fn,使用的state, 是闭包中的state
如果每次render要拿到最新的stata, 就要将state放在依赖数组上,否则读取的总是闭包中的state, 而不是最新的state
但是这样一来, 就因为新增了useCallback的依赖,失去了useCallback缓存fn,保持fn引用不变的初衷
export default function App() {
const [counter, setCounter] = useState(0);
const handleClick = useCallback(() => {
console.log(counter);
setCounter(counter => counter + 1);
}, []);
return (
<div onClick={handleClick}>
click to add counter
counter: {counter}
</div>
)
}
因此引入useEvent, 主要基于ref+useCallback实现useEvent来解决这个问题
// (!) 近似行为
function useEvent(handler) {
const handlerRef = useRef(null);
// 每次render都在ref中维持一个最新的handler引用,从而拿到最新的上下文环境以及状态。在实际实现时它会在 layout effect 之前执行
useLayoutEffect(()=>{
handlerRef.current = handler;
})
// 返回一个useCallback包裹的依赖数组长度为0的函数,在useCallback的缓存函数里调用handler
// 使得handler执行时是最新的上下文环境与状态,绕过了闭包陷阱,去掉了多余的deps,同时保证了onClick引用不变
return useCallback((...args) => {
const fn = handlerRef.current;
return fn(...args);
}, []);
}
function Chat() {
const [text, setText] = useState('');
// ✅ Always the same function (even if `text` changes)
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}