【问题标题】:cannot update parent value all together in React hooks useEffect with setTimeout update无法在 React 钩子 useEffect 和 setTimeout 更新中一起更新父值
【发布时间】:2021-08-15 08:11:30
【问题描述】:

我有两个组件,parentComponent 和 ChildrenComponent。当我单击该按钮时,它将呈现handleClick 方法以更新ChildrenComponent 中的childrenValue。在ChildrenComponent 内部,我使用useEffect 来观察childrenValue 的变化。

当我在useEffect 内部有一个计时器时,而不是在ParentComponent 上重新渲染一次,为什么每次父组件值更改时都会渲染它?

父组件:

import React, { useState } from "react";
import { ChildrenComponent } from "./ChildrenComponent";

export const ParentComponent = () => {
  const [test1, setTest1] = useState(false);
  const [test2, setTest2] = useState(false);
  const [test3, setTest3] = useState(false);

  console.log("ParentComponent render", test1, test2, test3);

  const handleChangeParentValue = () => {
    setTest1(true);
    setTest2(true);
    setTest3(true);
  };

  return (
    <>
      <ChildrenComponent handleChangeParentValue={handleChangeParentValue} />
    </>
  );
};

子组件:

import React, { useState, useEffect } from "react";

export const ChildrenComponent = (props) => {
  const { handleChangeParentValue } = props;
  const [childrenValue, setChildrenValue] = useState(false);

  useEffect(() => {
    if (childrenValue) {
      // handleChangeParentValue(); 
      // if I use handleChangeParentValue() instead of a timer, 
      // the parent component only render once with three true value: 
      // ParentComponent render true true true
      const timerSuccess = setTimeout(() => {
        handleChangeParentValue();
      }, 1000);
      return () => {
        clearTimeout(timerSuccess);
      };
    }
  }, [childrenValue]);

  const handleClick = () => {
    setChildrenValue((prev) => !prev);
  };
  return (
    <>
      <div>ChildrenComponent</div>
      <button onClick={handleClick}>CLick me</button>
    </>
  );
};

当我使用计时器时,每次 setTest 运行时父级都会渲染:

ParentComponent render true false false
ParentComponent render true true false
ParentComponent render true true true

为什么会这样?如果我想使用子组件上的计时器进行值延迟更新,但仍希望父组件渲染一次或一起而不是单独更新父值,我该怎么办?

【问题讨论】:

  • 我对你的问题有点困惑。你是在问为什么当你的状态改变时反应会重新渲染父组件?组件中调用的任何 setState 都会强制重新渲染该组件,即使值相同。
  • 没有。我知道父组件会重新渲染。我想知道为什么当我有计时器时它会重新渲染 3 次。如果没有那个计时器,它只会重新渲染一次。

标签: javascript reactjs settimeout use-effect


【解决方案1】:

更新(2021 年 7 月 15 日)

来自Automatic batching for fewer renders in React 18:

从带有 createRoot 的 React 18 开始,所有更新都将是 自动批处理,无论它们来自何处。

这意味着在超时、承诺、原生事件中更新 处理程序或任何其他事件将以与内部更新相同的方式进行批处理 反应事件。


您正在通过异步调用handleChangeParentValue 函数来防止将状态更新一起批处理。

当多个状态设置器函数被异步调用时,它们不会批处理在一起。这意味着每次调用 state setter 函数时父组件都会重新渲染。

您有 3 次调用 state setter 函数,因此每次调用父组件都会重新渲染,即 3 次。

调用handleChangeParentValue同步会导致多个状态更新批处理一起,导致父组件只重新渲染一次。

下面的演示展示了这种行为。

function App() {
  const [state1, setState1] = React.useState({});
  const [state2, setState2] = React.useState({});
  const [state3, setState3] = React.useState({});

  console.log("App component rendered");
  
  const asyncStateUpdate = () => {
    console.log("triggering state updates asynchronously not batched");
    
    setTimeout(() => {
      setState1({});
      setState2({});
      setState3({});
    }, 1000);
  };
  
  const syncStateUpdate = () => {
     console.log("triggering state updates synchronously - batched");
     setState1({});
     setState2({});
     setState3({});
  };
  
  return (
    <div>
      <button onClick={asyncStateUpdate}>Trigger State Update Asynchronously</button>
      <button onClick={syncStateUpdate}>Trigger State Update Synchronously</button>
    </div>
  );
}

ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>

注意:状态更新本身是异步的,但您必须同步触发状态更新才能将多个状态更新一起批处理。

解决方案

您可以将三个独立的状态组合成一个状态。因此,您只需调用一个状态设置器函数。这样父组件只会重新渲染一次,因为只有一个状态设置器函数调用。

更改以下内容

const [test1, setTest1] = useState(false);
const [test2, setTest2] = useState(false);
const [test3, setTest3] = useState(false);

到

const [state, setState] = useState({
  test1: false,
  test2: false,
  test3: false
});

并将handleChangeParentValue 函数中的三个状态设置器函数调用替换为一个状态设置器函数调用:

const handleChangeParentValue = () => {
   setState({
      test1: true,
      test2: true,
      test3: true
   });
};

【讨论】:

  • 所以你的意思是即使在定时器内部,我只调用了一个函数,但是在函数内部,由于这个异步调用,组件每次遇到setState时都会重新渲染。这就是它重新渲染 3 次的原因。我的理解正确吗?
  • 是的。您正在通过异步调用 handleChangeParentValue 来防止多个状态更新被批量处理。
猜你喜欢
  • 2019-09-23
  • 1970-01-01
  • 2020-09-02
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 2021-12-01
  • 1970-01-01
  • 2020-01-03
相关资源
最近更新 更多