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

被useCallback包裹的onSelect函数引发关联组件重复渲染问题

问题:useCallback包裹的函数引发未修改组件不必要重渲染

相关代码

状态与回调函数

const [descriptive, setDescriptive] = useState({})

const handleSelectionsCall = useCallback((event, inputLabel) => {
  const { target: { value }} = event;
  var newVal = typeof value === "string" ? value.split(",") : value;
  const lastItem = newVal[newVal.length -1]
  if (lastItem === 'Unselect') newVal = []
  const update = descriptive[inputLabel];
  const updatedDescriptive = {
    ...descriptive,
    [inputLabel]: {
      ...update,
      selected: newVal,
      error: newVal.length === 0 && ["Exchange", "Industry", "Sector"].includes(inputLabel) ? "At least one selection must be made" : ""
    }
  };
  setDescriptive(updatedDescriptive);
}, [descriptive, setDescriptive]);

三个CheckSelect组件调用

<div className={colSize}>
  <CheckSelect
    selected={descriptive.Sector.selected}
    allSelections={allSectors}
    allSelected={
      descriptive.Sector.selected.length === allSectors.length
    }
    inputLabel="Sector"
    onSelect={handleSelectionsCall}
    labelFontSize="120%"
    extraOutlineSpace="fil"
    validationWarning={descriptive.Sector.error}
    size={smallWidth ? "small" : "medium"}
  />
</div>
<div className={colSize}>
  <CheckSelect
    selected={descriptive.Industry.selected}
    allSelections={descriptive.Industry.allSelections}
    allSelected={
      descriptive.Industry.selected.length === descriptive.Industry.allSelections.length
    }
    inputLabel="Industry"
    onSelect={handleSelectionsCall}
    labelFontSize="120%"
    extraOutlineSpace="fill"
    validationWarning={descriptive.Industry.error}
    size={smallWidth ? "small" : "medium"}
  />
</div>
<div className={colSize}>
  <CheckSelect
    selected={descriptive.Exchange.selected}
    allSelections={exchanges}
    allSelected={descriptive.Exchange.selected.length === 2}
    inputLabel="Exchange"
    onSelect={handleSelectionsCall}
    labelFontSize="120%"
    extraOutlineSpace="filll"
    validationWarning={descriptive.Exchange.error}
    size={smallWidth ? "small" : "medium"}
  />
</div>

记忆化后的CheckSelect组件

export default memo(function CheckSelect({
    allSelections,
    selected,
    onSelect,
    inputLabel='',
    labelFontSize,
    extraOutlineSpace='',
    validationWarning='test',
    size,
    allSelected=false
  }) {
    console.log('render ' + inputLabel)

return (
<Select
  labelId="demo-multiple-checkbox-label"
  id="demo-multiple-checkbox"
  sx={selectFieldStyles(validationWarning)}
  multiple
  value={selected}
  onChange={e => onSelect(e, inputLabel)}
  input={<OutlinedInput label={inputLabel+extraOutlineSpace} />}
  renderValue={(selected) => {
    if (allSelected) return 'Any'
    return selected.join(', ')
  }}
  MenuProps={MenuProps}
>
{/* 渲染选项的map逻辑 */}
</Select>
)
})

问题现象

选中任意一个CheckSelect的选项时,另外两个未修改的组件也会触发重渲染,且它们的props(除了onSelect)并未发生变化。移除onSelect传递后,该现象消失。


原因分析与解决办法

核心原因

useCallback的依赖数组包含了descriptive这个state对象。每次选中选项时,你通过...descriptive创建了新的descriptive对象,导致useCallback的依赖项更新,生成新的handleSelectionsCall函数实例。

memo默认做浅比较,函数引用的变化会被判定为props更新,因此另外两个未修改的CheckSelect组件会触发重渲染。

解决方案

方案1:使用函数式更新,移除descriptive依赖

将setDescriptive改为函数式更新,直接使用React传入的最新状态,这样useCallback的依赖只需保留稳定的setDescriptive:

const handleSelectionsCall = useCallback((event, inputLabel) => {
  const { target: { value }} = event;
  var newVal = typeof value === "string" ? value.split(",") : value;
  const lastItem = newVal[newVal.length -1];
  if (lastItem === 'Unselect') newVal = [];

  setDescriptive(prevDescriptive => {
    const update = prevDescriptive[inputLabel];
    return {
      ...prevDescriptive,
      [inputLabel]: {
        ...update,
        selected: newVal,
        error: newVal.length === 0 && ["Exchange", "Industry", "Sector"].includes(inputLabel) ? "At least one selection must be made" : ""
      }
    };
  });
}, [setDescriptive]);

setDescriptive是React在组件初始化时生成的稳定函数,不会随渲染改变,因此handleSelectionsCall的引用始终不变,未修改的CheckSelect组件不会触发不必要的重渲染。

方案2:自定义memo比较函数(不推荐)

如果不想修改回调函数逻辑,可以给memo传入自定义比较函数,忽略onSelect的变化:

export default memo(function CheckSelect(props) {
  // 组件原有逻辑
}, (prevProps, nextProps) => {
  // 手动比较除onSelect外的所有props
  return (
    prevProps.selected === nextProps.selected &&
    prevProps.allSelections === nextProps.allSelections &&
    prevProps.allSelected === nextProps.allSelected &&
    prevProps.inputLabel === nextProps.inputLabel &&
    prevProps.labelFontSize === nextProps.labelFontSize &&
    prevProps.extraOutlineSpace === nextProps.extraOutlineSpace &&
    prevProps.validationWarning === nextProps.validationWarning &&
    prevProps.size === nextProps.size
  );
});

此方案需手动维护比较逻辑,容易遗漏props更新,优先推荐方案1。


内容的提问来源于stack exchange,提问作者Logan McNulty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 09:27:02