You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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;

问题原因

  1. 父组件重渲染触发子组件默认重渲染:父组件更新selectedUnits状态后会触发自身重渲染,默认情况下,子组件会跟随父组件一起重渲染,即使子组件的props没有变化。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:39:52