如何使用useCallback对高阶函数进行Memoize以避免子组件重渲染?
嘿,这个场景确实很容易踩坑——你用useCallback包裹了那个函数生成器memoizedFnCreator,它本身的引用是稳定的,但每次调用它(比如memoizedFnCreator(id))都会返回一个全新的函数实例,这就导致传给ChildComponent的onClick props每次渲染都变,哪怕你给子组件套了React.memo也会触发不必要的重渲染。下面给你几个实用的解决方案:
方案一:用useMemo缓存每个id对应的函数集合
我们可以提前为数组里的每个id生成稳定的函数引用,把这些函数存在一个对象里,确保每个id对应的函数引用不会随父组件渲染而变化:
import React, { useCallback, useMemo } from 'react'; const MyComponent = ({ dependancies, anArray }) => { // 先定义核心的点击处理逻辑,用useCallback确保自身引用稳定 const handleClick = useCallback((id) => { /* 这里写你的点击逻辑,比如根据id执行对应操作 */ console.log(`处理id为${id}的点击事件`); }, [dependancies]); // 用useMemo为每个id生成稳定的函数引用 const memoizedClickHandlers = useMemo(() => { return anArray.reduce((handlerMap, { id }) => { handlerMap[id] = () => handleClick(id); return handlerMap; }, {}); }, [anArray, handleClick]); // 依赖数组只有在anArray或handleClick变化时才更新 return ( <div> {anArray.map(({ id }) => ( <ChildComponent key={id} onClick={memoizedClickHandlers[id]} /> ))} </div> ); }; // 别忘了给子组件加上React.memo,确保props不变时不重渲染 const ChildComponent = React.memo(({ onClick }) => { console.log('子组件渲染'); return <button onClick={onClick}>点击我</button>; });
这个方案的核心是利用useMemo把每个id对应的函数缓存起来,只有当anArray或者handleClick本身变化时,才会重新生成整个handler集合,保证了传给子组件的onClick引用稳定。
方案二:让子组件接收id和通用处理函数,而非绑定后的函数
换个思路,父组件不用提前绑定id到函数上,而是把id和通用的处理函数一起传给子组件,让子组件内部完成绑定:
import React, { useCallback } from 'react'; const MyComponent = ({ dependancies, anArray }) => { // 通用的点击处理函数,用useCallback保证引用稳定 const handleItemClick = useCallback((id) => { /* 处理逻辑 */ console.log(`处理id为${id}的点击事件`); }, [dependancies]); return ( <div> {anArray.map(({ id }) => ( <ChildComponent key={id} itemId={id} onItemClick={handleItemClick} /> ))} </div> ); }; // 子组件用React.memo包裹,props不变则不重渲染 const ChildComponent = React.memo(({ itemId, onItemClick }) => { console.log('子组件渲染'); return <button onClick={() => onItemClick(itemId)}>点击我</button>; });
这里父组件传给子组件的onItemClick是稳定的,itemId也是数组项里的固定值,所以子组件的props不会随父组件渲染而变化。虽然子组件内部的onClick每次渲染都会生成新函数,但因为React.memo的存在,只要props不变,子组件就不会重渲染。
方案三:封装自定义Hook复用缓存逻辑
如果你的项目里经常遇到这类场景,可以把缓存逻辑封装成自定义Hook,让代码更简洁易复用:
import React, { useCallback, useMemo } from 'react'; // 自定义Hook:为数组项生成稳定的处理函数集合 const useMemoizedItemHandlers = (items, handler, dependencies) => { const stableHandler = useCallback(handler, dependencies); return useMemo(() => { return items.reduce((handlerMap, { id }) => { handlerMap[id] = () => stableHandler(id); return handlerMap; }, {}); }, [items, stableHandler]); }; // 组件中使用 const MyComponent = ({ dependancies, anArray }) => { const memoizedClickHandlers = useMemoizedItemHandlers( anArray, (id) => { /* 处理逻辑 */ console.log(`处理id为${id}的点击事件`); }, [dependancies] ); return ( <div> {anArray.map(({ id }) => ( <ChildComponent key={id} onClick={memoizedClickHandlers[id]} /> ))} </div> ); }; const ChildComponent = React.memo(({ onClick }) => { console.log('子组件渲染'); return <button onClick={onClick}>点击我</button>; });
关键提醒
无论用哪个方案,都必须给ChildComponent加上React.memo,否则就算props不变,子组件还是会随父组件渲染而重新渲染,memoize的效果就白费了。
内容的提问来源于stack exchange,提问作者codingedward

