
它缓存的是函数引用
const handleSubmit = useCallback(orderDetails => { post('/orders', { productId, orderDetails })}, [productId])函数字面量每次渲染都是新对象。这件事本身无害,创建函数很快。只有当这个引用被用来判断「要不要重做某件事」时,稳住它才有意义。
两种情况值得用
第一种,传给被 memo 包起来的子组件:
const List = memo(function List({ items, onSelect }) { // ...})
function App({ items }) { const [query, setQuery] = useState('')
const handleSelect = useCallback(id => { // ... }, [])
return <List items={items} onSelect={handleSelect} />}子组件没被 memo 包,或者 props 里还有别的每次都新的值,这个 useCallback 就起不到作用。
第二种,作为 Effect 或其他 Hook 的依赖:
const load = useCallback(async () => { const res = await fetch(`/api/users/${userId}`) setUser(await res.json())}, [userId])
useEffect(() => { load()}, [load])这段更常见的简化写法是把请求直接写进 Effect 里,连这层都省掉。真需要把函数往外传时才用 useCallback。
与 useEffectEvent 的分工
如果这个函数其实是「某个动作」,只是被 Effect 用到,用 useEffectEvent 更准:它能读到最新的 props 与 state,又不必进依赖数组。
与 useMemo 的关系
useCallback(fn, deps) 等价于 useMemo(() => fn, deps)。缓存值用 useMemo,缓存函数用 useCallback,后者只是省掉一层嵌套函数,别的没有区别。
React Compiler 之后
编译器会自动记忆化函数(见 useMemo 参考页开头的注记),新代码不必默认手写。开了编译器的项目里,useCallback 主要留给需要精确控制引用的地方。
四条边界
依赖要写全。lint 的 exhaustive-deps 规则会检查;漏了会读到旧值,多了会每次重建,等于没缓存。
官方不主张到处用它。给一个没包 memo 的子组件传 useCallback 包过的函数,只是白加一层。
它不改变执行结果,只稳住引用。
依赖里放了每次新建的对象或函数,缓存照样失效。要么把那个值也稳住,要么把它移到组件外,或者干脆塞进 Effect 里。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
最后更新于 ,距今已过 190 天
部分内容可能已过时