关于useEvent以及它解决了什么问题

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} />;
}
posted @ 2022-07-08 16:34  IslandZzzz  阅读(120)  评论(0编辑  收藏  举报