使用useCallback后React子组件仍重复渲染问题排查
解决React子组件不必要重渲染的问题
问题场景
父组件App.jsx中通过useCallback定义updateSelectedUnits函数并传递给子组件Table.jsx,点击Table的TD触发该函数更新父组件selectedUnits状态(该状态并未传递给子组件),但Table仍会重复渲染,需要阻止这种不必要的重渲染。
相关代码
App.jsx
export default function App() { const [selectedUnits, setSelectedUnits] = useState([]); const updateSelectedUnits = useCallback((unitId) => { const newSelectedUnits = selectedUnits.includes(unitId) ? selectedUnits.filter((u) => u !== unitId) // remove item : [...selectedUnits, unitId]; setSelectedUnits(newSelectedUnits); }, []); return ( <div className="App"> <Table updateSelectedUnits={updateSelectedUnits} /> </div> ); }
Table.jsx
import React from "react"; const Table = ({ updateSelectedUnits }) => { const handleUnitClick = (event, unitId) => { //add class to td updateSelectedUnits(unitId); }; console.log("table rerendered"); return ( <table> <tbody> <tr> <td onClick={(e) => handleUnitClick(e, "1")}>01</td> </tr> <tr> <td onClick={(e) => handleUnitClick(e, "1")}>02</td> </tr> </tbody> </table> ); }; export default Table;
问题原因
- 父组件重渲染触发子组件默认重渲染:父组件更新
selectedUnits状态后会触发自身重渲染,默认情况下,子组件会跟随父组件一起重渲染,即使子组件的props没有变化。 - useCallback依赖项错误:当前
useCallback的依赖项为空数组,导致函数内部捕获的selectedUnits始终是初始的空数组,不仅会造成逻辑错误(点击TD时无法正确移除已选中的unitId),还会让updateSelectedUnits的逻辑不符合预期。
解决方案
步骤1:修复useCallback的逻辑和依赖项
有两种方式可以解决闭包陷阱问题:
方式一:添加正确的依赖项
将selectedUnits加入useCallback的依赖数组,确保函数能获取到最新的状态值:
const updateSelectedUnits = useCallback((unitId) => { const newSelectedUnits = selectedUnits.includes(unitId) ? selectedUnits.filter((u) => u !== unitId) : [...selectedUnits, unitId]; setSelectedUnits(newSelectedUnits); }, [selectedUnits]);
方式二:使用函数式更新(推荐)
通过setSelectedUnits的函数式更新语法,直接基于前一次状态计算新状态,无需依赖selectedUnits:
const updateSelectedUnits = useCallback((unitId) => { setSelectedUnits(prevSelected => { return prevSelected.includes(unitId) ? prevSelected.filter(u => u !== unitId) : [...prevSelected, unitId]; }); }, []);
步骤2:用React.memo包裹子组件
使用React.memo高阶组件包裹Table组件,让子组件仅在props发生变化时才重渲染:
import React, { memo } from "react"; const Table = ({ updateSelectedUnits }) => { const handleUnitClick = (event, unitId) => { //add class to td updateSelectedUnits(unitId); }; console.log("table rerendered"); return ( <table> <tbody> <tr> <td onClick={(e) => handleUnitClick(e, "1")}>01</td> </tr> <tr> <td onClick={(e) => handleUnitClick(e, "1")}>02</td> </tr> </tbody> </table> ); }; export default memo(Table);
效果验证
完成上述修改后,点击Table的TD时,父组件状态更新,但由于updateSelectedUnits的引用保持稳定(方式二的useCallback依赖为空数组,函数引用不会变化;方式一的依赖变化时才会更新函数引用),且Table组件被React.memo包裹,只有当props真正变化时才会重渲染,从而避免了不必要的重复渲染。
内容的提问来源于stack exchange,提问作者Aayush Dahal
相关产品推荐
相关产品推荐

