父组件未用memo包裹时,useCallback包裹子组件渲染函数是否有意义?
关于React中useCallback的两个疑问解答
问题背景
根据React官方文档的理解,阻止父组件的子组件重渲染,需用memo包裹子组件,并用useCallback包裹父组件传递给子组件的函数。但针对给出的代码实现有以下疑问:
代码示例
const ParentComponent = () => { const [isFiltersOpen, setIsFiltersOpen ] = useState(false); const hasNoDevices = someHookReturnFromApi(); const hasDevices = someHookReturnFromApi(); const areAllDevicesLoaded = anotherHookButCallsApiForData(); const missingOtherStuff = thisHookChecksIfSomeApisAreDoneLoading(); const onSomething = useCallback(() => console.log(hasNoDevices), [hasNoDevices]); const sortedData = useMemo(() => hasDevices.sort(/* do some sorts... */), [hasDevices]); const filters = filtersFromApi(); const onClosePanel = () => setIsFiltersOpen(false); const renderSection = useCallback(() => { if (hasNoDevices) { return ( <EmptyState areAllDevicesLoaded={areAllDevicesLoaded} subtitle={t('just.a.translation')} /> ); } if (missingOtherStuff) { return ( <SectionEmptyState onSomething={onSomething} /> ); } return ( <> <SectionData sortedData={sortedData} hasDevices={hasDevices} /> <div> {isFiltersOpen && <Panel isOpen={isFiltersOpen} filters={filters} onClose={onClosePanel} />} </div> </> ); }, [hasDevices, hasNoDevices, sortedData, areAllDevicesLoaded, onSomething, isFiltersOpen, filters, onClosePanel]); return ( <> <h1>Parent component</h1> {renderSection()} </> );
疑问解答
1. useCallback返回相同引用的作用是什么?和内存分配有关吗?
useCallback的核心作用就是在依赖不变的情况下,返回同一个函数引用,主要有两个层面的意义:
- 避免子组件不必要重渲染:如果这个函数作为props传给了用
memo包裹的子组件,子组件会对比props的引用是否变化。如果不用useCallback,父组件每次重渲染都会创建新的函数实例(引用变了),子组件就会跟着重渲染;用了useCallback后,只要依赖没更新,函数引用不变,子组件就不会因为这个props变化而重渲染。 - 减少内存分配开销:虽然现代JS引擎对临时对象的回收效率很高,但频繁创建新函数实例还是会产生微小的内存分配和GC压力。
useCallback能在依赖稳定时复用同一个函数,确实能减少这部分开销,但这通常是次要收益,主要价值还是配合memo优化子组件渲染。
2. 父组件未用memo包裹时,用useCallback包裹子组件渲染函数(比如const componentVariable = useCallback(() => <SomeComponentMaybeWithProps />, []))是否有意义?
几乎没有意义,原因如下:
- 父组件没加
memo的话,只要父组件自身触发重渲染(比如state更新、props变化),整个父组件的渲染逻辑都会重新执行一遍。哪怕你用useCallback包了renderSection这个渲染函数,父组件重渲染时还是会调用renderSection(),而函数内部返回的JSX元素会被重新创建(因为函数内部的变量、props可能已经变化,而且就算没变化,JSX本质是React.createElement调用,每次执行都会生成新的元素对象)。 - 这种场景下,
useCallback只是保证了renderSection这个函数的引用不变,但父组件每次重渲染都会执行这个函数生成新的JSX,根本达不到阻止子组件重渲染的目的。只有当这个渲染函数被作为props传给其他用memo包裹的组件时,useCallback才有意义——否则纯粹是多此一举,甚至可能因为维护依赖数组增加额外的心智负担。
内容的提问来源于stack exchange,提问作者Nick09
相关产品推荐
相关产品推荐

