React 功能组件中超出了最大更新深度

React Maximum update depth exceeded in functional component

在我的 React 功能组件中,我执行了一个函数来将复选框切换为选中或未选中。它工作正常。

函数(在父组件中)是这样的:

const ParentComponent = () => {

const { state, dispatch } = useContext(FilterContext);

const onChange = useCallback((value) => {
(item) => {
  const { name, selected } = item;
 
  // this "name" comes from another sibling component on the mount, and then from child component (sideAccordian) on click

  const selectedFilters = state.identifiers_checkbox.selected_checkboxes.find(
    (obj) => obj === name
  );

  const selected_checkboxes = selectedFilters
    ? state.identifiers_checkbox.selected_checkboxes.filter(
        (obj) => obj !== name
      )
    : [...state.identifiers_checkbox.selected_checkboxes, name];

  const toggled_checkboxes = {
    selected_checkboxes,
    options_checkbox: [
      ...state.identifiers_checkbox.options_checkbox.map((filter) => {
        return {
          ...filter,
          options: filter.options.map((option, i) => {
            return name === option.name
              ? {
                  ...option,
                  selected,
                }
              : option;
          }),
        };
      }),
    ],
  };

  dispatch({
    type: "update-checkboxes",
    payload: {
      checkboxSelected: true,
      selected_checkboxes: toggled_checkboxes.selected_checkboxes,
      options_checkbox: toggled_checkboxes.options_checkbox,
    },
  });
},
[
  dispatch,
  state.identifiers_checkbox.options_checkbox,
  state.identifiers_checkbox.selected_checkboxes,
]);

  useEffect(() => {
    if (info.prevui === "intervention") {
      if (state.identifiers_checkbox.options_checkbox.length > 1) {
        onChange({ name: info.abbr, selected: 1 });
      }
    }
  }, [
    info.abbr,
    info.prevui,
    dispatch,
    state.identifiers_checkbox.options_checkbox.length,
    // onChange,
  ]);


return (
       <div>
        {
            state.identifiers_checkbox.options_checkbox.length > 1 ? (
             <SideAccordian
               filterstate={state.identifiers_checkbox}
               onChange={onChange}
             />
             ) : (
                <h6 style={{ textAlign: "center" }}>No Options Available</h6>
             )
         }
        </div>
    )
}

子组件

export default function SideAccordian(props) {
    const [options, setOptions] = useState([]);
    const classes = useStyles();

    useEffect(() => {
        setOptions(props.filterprop);
    }, [props]);

    return (
        <div className={classes.root}>
            {
                options.map((ele, id) => (
                    <Accordion key={id} defaultExpanded={true}>
                        <AccordionSummary
                            key={ele.key}
                            expandIcon={<ExpandMoreIcon />}
                            aria-controls="panel1a-content"
                            id="panel1a-header"
                        >
                        </AccordionSummary>
                        {ele.options.map((item, i) => {
                            return (
                                <AccordionDetails key={i}>
                                    <IndeterminateInput
                                        key={i}
                                        id={i}
                                        name={item.name}
                                        value={item.label}
                                        selected={item.selected}
                                        onChange={props.onChange}
                                    />
                                </AccordionDetails>
                            )
                        })}
                    </Accordion>
                ))
            }
        </div>
    );
}

此外,我需要在组件安装时调用 onChange() 函数,然后根据作为参数传递的复选框的名称自动将任何复选框标记为已选中。

处理函数 onChange() 在单击复选框时工作正常,但是当尝试根据特定条件在装载时调用它时,它不起作用。

一旦安装组件:

如果 onChange() 包含在依赖数组中,它会抛出 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array or one of the dependencies changes on every render.

如果从依赖项数组中删除,它可以工作但会不断抛出错误 each Hook useEffect has a missing dependency: 'onChange'. Either include it or remove the dependency array

因为,我只需要在渲染器上执行一次这个函数,即(当 info.prevui === intervention' & info.prevui does not change every time ),这个错误行为 useEffect() 超出我的理解。

任何人都可以帮助理解并提供任何工作hint/solution吗?

谢谢

在效果依赖项中包含 onChange 处理程序时代码中存在无限渲染循环的原因是,每当调用 handleCheckboxChange 函数时, handleCheckboxChange由于依赖数组中的 state.identifiers_checkbox.selected_checkboxesstate.identifiers_checkbox.options_checkbox 函数被重新定义,因为每次调用 handleCheckboxChange 时它们都会在 redux 状态下更新。

然后,当您将 onChange 传递到 useEffect 的依赖数组时,它 运行 每次 onChange 更改时都会产生效果,即每次 handleCheckboxChange 是 运行 - 因此无限渲染循环。

所以,解决这个问题的最简单方法是将 onChange 放入子组件的 ref 中,这样你就不需要将它包含在依赖数组中,这样它仍然是 up-to-date 在效果中,并且不会导致厄运的无限渲染循环。

  const onChangeRef = useRef(onChange);
  useEffect(()=>{onChangeRef.current = onChange},[onChange])
  useEffect(() => {
    if (info.prevui === "intervention") {
      if (state.identifiers_checkbox.options_checkbox.length > 1) {
        onChangeRef.current({ name: info.abbr, selected: 1 });
      }
    }
  }, [
    info.abbr,
    info.prevui,
    dispatch,
    state.identifiers_checkbox.options_checkbox.length,
  ]);

例如:您在聊天中发布的 codesandbox(useEffect 中的 handleCheckBoxChange 会导致无限循环,因为每次组件 re-renders 时都会创建 handleCheckboxChange: https://codesandbox.io/s/react-functional-component-forked-g7vh1

  useEffect(() => {
    handleCheckboxChange("ornge");

    setTimeout(() => {
      handleCheckboxChange("tmto");
    }, 5000);
  }, [handleCheckboxChange]);

然后修复: https://codesandbox.io/s/infinite-loop-fix-4y6zm?file=/src/index.js

  const changeRef = useRef(handleCheckboxChange);
  useEffect(() => {
    changeRef.current = handleCheckboxChange;
  }, [handleCheckboxChange]);
  useEffect(() => {
    changeRef.current("ornge");

    setTimeout(() => {
      changeRef.current("tmto");
    }, 5000);
  }, []);

按要求详细说明:

React 的 useRef 挂钩 returns 一个可变的值,并且将始终保持引用稳定。本质上对于每个渲染,changeRef 总是指向内存中与之前相同的对象,因此它所指的是稳定的。这与大多数事情不同,因为例如 []===[] returns false 因为数组引用内存中的不同对象。

React 知道

useRef 可以创建这些可变的引用稳定对象,因此他们不需要将它们传递到任何挂钩的依赖数组中,因为他们知道这不是必要的。

通过将 onChange 函数填充到 ref 中,然后确保在 onChange 通过浅等式更改时更新该 ref,现在我们有一种方法可以引用最新版本的 onChange 而不必担心将其添加到效果的依赖项中。

React 将这些依赖数组 re-run 用于各种挂钩的原因是因为 Closures - 每次函数组件渲染时,都会重新创建所有各种函数和对象(尽管 useMemouseCallbackuseState 允许某些内容不会每次都重新创建)。当一个函数被创建时,它就有了一种方法来引用函数可以看到的范围内的所有变量。

这就是为什么像下面这样的东西起作用的原因。即使 testfunct 被调用之前超出范围,funct 周围有一个闭包,所以只要 funct 在某个地方的范围内,它总是可以看到它。

let funct;
{
 const test = 5;
 funct = ()=>{console.log(test)}
}
funct(); // logs 5

这些闭包是 React Hooks 所依赖的。这也是为什么 eslint 插件 react hooks 有 exhaustive-deps 部分的原因,因为如果你不包含某些东西,那么在使用该值时,它会变成 stale.

React 的 useRef 钩子允许我们绕过闭包,因为 refs 可以被改变,所以使用 ref.current 给我们无论闭包何时创建的任何值.