useCallBack 包装的 onSelect 函数导致所有将此函数作为 prop 的组件重新渲染

问题描述 投票:0回答:1

我有以下 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]);

我将其传递给这三个下拉检查选择 Material UI 组件。

<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 selections}
</Select>
)
})

当我更改其中一个选择时,其他两个选择会重新渲染,即使它们的道具没有改变。为什么会出现这种情况?

我知道这与handleSelectionsCall有关,因为如果我将其注释掉,以免传递给CheckSelect,则当更新另一个CheckSelect时,其他CheckSelect不会重新呈现。

谢谢!

javascript reactjs react-hooks state
1个回答
0
投票

问题是您已将

descriptive
放入依赖项列表中,并且您的函数始终调用
setDescriptive
,这会更新
descriptive
,这意味着必须创建一个新函数。所以
useCallback
没有任何有用的目的。

为了避免这种情况,不要直接在函数中使用状态成员

descriptive
;使用
setDescriptive
的回调形式来代替:

const handleSelectionsCall = useCallback(
    (event, inputLabel) => {
        const {
            target: { value },
        } = event;
        const newVal = typeof value === "string" ? value.split(",") : value;
        const lastItem = newVal[newVal.length - 1];
        if (lastItem === "Unselect") {
            newVal = [];
        }
        setDescriptive((descriptive) => { // ***
            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"
                            : "",
                },
            };
            return updatedDescriptive;
        });
    },
    [] // ***
);

请注意,我已从依赖项列表中删除了

descriptive
setDescriptive
,因为:

  • descriptive
    - 函数不再使用它,而是使用回调的参数。
  • setDescriptive
    - 来自
    useState
    的状态设置器是 稳定,因此没有理由将它们列在依赖项列表中。

现在,你的

handleSelectionsCall
已经稳定了,不会导致不必要的重新渲染。

© www.soinside.com 2019 - 2024. All rights reserved.