使用useCallback后子组件仍重渲染?为何需搭配React.memo?
为什么useCallback必须搭配React.memo才能阻止子组件重渲染?
问题描述
我正在研究useCallback和useMemo的工作原理及其对React应用性能的优化作用。为此我创建了一个简单Demo,发现传递给子组件的OnBlurHandler回调会导致父组件重渲染时子组件也随之重渲染。于是我用useCallback包裹该回调以避免子组件重渲染,但出乎意料的是子组件依然会重渲染。直到我用React.memo包裹子组件后,才实现了预期的渲染优化效果。我无法理解为何仅使用useCallback无法阻止子组件重渲染,为何必须搭配React.memo?
示例代码
父组件
// Parent Component import React, { useState, useEffect, useMemo, useCallback } from "react"; import Input from "./Input"; const UnderStadingHooks = () => { console.log("Component Start"); const [a, setA] = useState(0); const [b, setB] = useState(0); const [rdm, setRdm] = useState(0); let c; useEffect(() => { console.log("UseEffect Start"); c = a + b; console.log(a, b, c, d); }, [a, b]); const d = useMemo(() => { console.log("UseMemo Called"); return a + b; }, [a, b]); const OnBlurHandler = useCallback( (e) => { console.log("OnBLur Hablder called"); if (e.target.name === "a") { setA(parseInt(e.target.value)); } else { setB(parseInt(e.target.value)); } }, [a, b] ); const handleOnClick = () => { const ran = Math.ceil(Math.random() * 5); setRdm(ran); }; console.log("Component END"); return ( <div> <h1>Understanding Hooks In Depth</h1> <Input name="a" placeholder="Enter a Value of A" OnBlurHandler={OnBlurHandler} /> <Input name="b" placeholder="Enter a value of B" OnBlurHandler={OnBlurHandler} /> <p> Values are :{a},{b} </p> <p>SumEffect:{c}</p> <p>SumMemo:{d}</p> <button type="button" onClick={handleOnClick}> Display Some Random Number </button> <p>{rdm}</p> </div> ); }; export { UnderStadingHooks };
子组件
// Child Component import React, { memo } from "react"; const Input = ({ name, placeholder, id, OnBlurHandler }) => { console.log("Input Component Rendered"); return ( <input id={id} name={name} placeholder={placeholder} onBlur={OnBlurHandler} /> ); }; export default memo(Input);
解答
核心逻辑拆解
- React默认重渲染规则:React的默认行为是,只要父组件触发重渲染,它所有的子组件都会跟着重渲染——不管子组件接收的props有没有变化。这是为了确保UI状态和数据完全同步,避免出现不一致的情况。
- useCallback的真实作用:useCallback只是帮你缓存函数的引用。当父组件重渲染时,只要useCallback的依赖数组没变化,它就会返回同一个函数对象。但它并没有改变子组件“父重渲染则必重渲染”的默认规则。
- React.memo的关键作用:React.memo是一个高阶组件,它会给子组件加上props对比逻辑:每次父组件重渲染后,它会浅比较子组件前后两次接收的props,如果props完全没变化,就跳过子组件的重渲染;如果props变了,才会触发子组件更新。
结合你的Demo分析
在你没给子组件加React.memo的时候,就算用useCallback缓存了OnBlurHandler,当父组件通过setRdm更新状态触发重渲染时,子组件依然会跟着重渲染——因为React根本不会去检查子组件的props有没有变化,默认就是父重子必重。
当你给子组件加上React.memo后,它会对比前后传入的所有props:name、placeholder没变化,OnBlurHandler因为被useCallback缓存了,引用也没变化,所以props对比通过,子组件就不会触发重渲染了。
总结
useCallback负责保证props的稳定性,让传递给子组件的回调引用不变;React.memo负责开启props对比机制,让子组件在props不变时跳过重渲染。两者必须配合使用:
- 没有useCallback,就算有React.memo,每次父组件重渲染都会生成新的回调函数,props对比失败,子组件还是会重渲染;
- 没有React.memo,就算useCallback保证了props不变,父组件重渲染时子组件依然会无条件跟着重渲染。
内容的提问来源于stack exchange,提问作者Sandeep Garg
相关产品推荐
相关产品推荐

