从子级设置父级状态(对象)会导致不断的重新渲染

时间:2019-12-22 19:57:15

标签: javascript reactjs state react-hooks

我有一个父组件,其中有几个子组件。我希望能够根据孩子的状态设置父母的状态。

例如,假设一个有3个孩子的父组件都从服务器获得了他们的状态。然后,他们设置父母的身份,以便父母可以查看所有孩子是否有问题,或者只有几个。

父母:

const Parent = () => {
  const [hasIssues, setHasIssues] = useState({
    "child-item-1": false,
    "child-item-2": false,
    "child-item-3": false
  });

  const issuesHandler = (childName, childStatus) => {
    setHasIssues({ ...hasIssues, [childName]: childStatus });
  };

  return (
    <div>
      <pre>{JSON.stringify(hasIssues, null, 2)}</pre>
      <div>
        <ChildItemOne issuesHandler={issuesHandler} />
        <ChildItemTwo issuesHandler={issuesHandler} />
        <ChildItemThree issuesHandler={issuesHandler} />
      </div>
    </div>
  );
};

一个孩子的例子:

const ChildItemOne = ({ issuesHandler }) => {
  // Imagine this is actually retrieved from a server
  const hasIssues = Math.random() <= 0.75;

  issuesHandler("child-item-1", hasIssues);

  return <div>{`child-item-1: ${hasIssues}`}</div>;
};

当然,此示例将超过最大更新深度。我尝试通过使用以下示例来防止这种情况:

useEffect(() => {
  issuesHandler("child-item-1", hasIssues);
}, [hasIssues, issuesHandler]);

但是这仍然没有预期的结果,因为状态仍在不断更新,但不会超过最大更新深度:Codesandbox example

我也尝试过使用useCallback,它没有任何作用。将useEffect的依赖项更改为一个空数组(ESLint仍然不允许我在本地执行此操作),也无效:

enter image description here

从子组件设置父状态而不引起持续的重新渲染的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

这看起来像是useContext的情况。 React Context用于提供对父组件和子组件的状态/功能的访问,而无需使用回调地狱。

首先在应用程序中的某个位置创建一个虚拟上下文组件,然后将其导入到Parent组件,并使用它包装需要传递的组件。然后,您无需将状态/功能传递给孩子,只需将其传递给上下文组件即可。

在子组件中,您可以使用useContext访问这些数据。

issueContext.js(虚拟上下文文件)

// app/context/issueContext.js
import { createContext } from 'react';    
export default createContext();

父组件

import IssueContext from 'issueContext';

const Parent = () => {
  const [hasIssues, setHasIssues] = useState({ ... });    
  const issuesHandler = (childName, childStatus) => { ... };

  return (
    <>
      <IssueContext.Provider value={{ hasIssues, setHasIssues, issuesHandler }} >     
          <ChildItemOne />
          <ChildItemTwo />
          <ChildItemThree />
      </IssueContext>         
    </>
  );
};

子组件

import React, {useContext} from 'react';
import IssueContext from 'issueContext';

const ChildItemOne = () => {

  const {hasIssues, setHasIssues, issuesHandler} = useContext(IssueContext);

  if (something wrong) {
    sethasIssues();

    issuesHandler("child-item-1", hasIssues);  
  }
};

答案 1 :(得分:1)

const hasIssues = Math.random() <= 0.75;移至useEffect()回调中。这将防止重新渲染循环,因为该值只会生成一次。

这也将更好地模拟对服务器的调用,因为在安装组件时它只会发生一次。

const ChildItemOne = ({ issuesHandler, hasIssues }) => {      
  useEffect(() => {
    // Imagine this is actually retrieved from a server
    issuesHandler("child-item-1", Math.random() <= 0.75);
  }, [issuesHandler]);

  return <div>{`child-item-1: ${hasIssues}`}</div>;
};

父级也应将hasIssues返回给带有issuesHandler的子套useCallback(),并在setHasIssues()中使用更新程序回调。这样可以防止在每个渲染器上重新创建issuesHandler,进而导致调用子级useEffect(),依此类推...

const Parent = () => {
  const [hasIssues, setHasIssues] = useState({
    "child-item-1": false,
    "child-item-2": false,
    "child-item-3": false
  });

  const issuesHandler = useCallback((childName, childStatus) => {
    setHasIssues(state => ({ ...state, [childName]: childStatus }))
  }, [setHasIssues]);

  return (
    <div>
      <pre>{JSON.stringify(hasIssues, null, 2)}</pre>
      <div>
        <ChildItemOne issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-1']} />
        <ChildItemTwo issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-2']} />
        <ChildItemThree issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-3']} />
      </div>
    </div>
  );
};

实时示例:

const { useState, useCallback, useEffect } = React;

const ChildItemOne = ({ issuesHandler, hasIssues }) => {
  useEffect(() => {
    // Imagine this is actually retrieved from a server
    issuesHandler("child-item-1", Math.random() <= 0.75);
  }, [issuesHandler]);

  return <div>{`child-item-1: ${hasIssues}`}</div>;
};

const Parent = () => {
  const [hasIssues, setHasIssues] = useState({
    "child-item-1": false,
    "child-item-2": false,
    "child-item-3": false
  });

  const issuesHandler = useCallback((childName, childStatus) => {
    setHasIssues(state => ({ ...state, [childName]: childStatus }))
  }, [setHasIssues]);

  return (
    <div>
      <pre>{JSON.stringify(hasIssues, null, 2)}</pre>
      <div>
        <ChildItemOne issuesHandler={issuesHandler} hasIssues={hasIssues['child-item-1']} />
      </div>
    </div>
  );
};

ReactDOM.render(
  <Parent />,
  root
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>