【问题标题】:When does React re-render after a setState call made inside an event handler在事件处理程序中进行 setState 调用后,React 何时重新渲染
【发布时间】:2020-01-27 12:22:50
【问题描述】:

来自 React DOCS:

https://reactjs.org/docs/state-and-lifecycle.html

状态更新可能是异步的

React 可以将多个 setState() 调用批处理到单个更新中以提高性能。

这完全有道理。如果您有类似下面的函数,那么在每个setState 调用上重新渲染效率会非常低

const [state1,setState1] = useState(false);
const [state2,setState2] = useState(false);
const [state3,setState3] = useState(false);

function handleClick() {
  setState1(true);
  setState2(true);
  setState3(true);
}

因此,在上述情况下,我希望 React 将所有 3 个 setState 调用批处理到一个重新渲染中。它就是这样做的!

但我想知道的是:

一旦handleClick 完成,是否保证重新渲染立即 handleClick 已经完成运行?我的意思是立即,比如立即同步?

从我构建的这个 sn-p 看来,这是真的。在handleClick 完成后,React 将同步应用更新(重新渲染)。如果我的假设有误,请纠正我。

见下面的sn-p:

  • 尽可能快地点击 3 倍
  • handleClick 将调用 setState 并将记录当前的 props.counter
  • App 上有一个昂贵的循环,因此重新渲染需要很长时间
  • 您的点击速度将比 React 重新渲染整个应用程序的速度快得多
  • 但是你会看到props.counter每次都不一样,没有任何重复,即使你点击多次真的很快
  • 这意味着一旦你的第二次点击被处理(这将需要一段时间,因为昂贵的循环),React 已经重新渲染了整个事情,handleClick 函数已经用 @ 的新值重新创建了来自 counter 更新状态的 987654334@。
  • 尝试快速点击 5 次,您会发现行为是一样的。

问题

setState 在事件处理函数内部进行调用时,一旦该处理函数完成运行,是否可以保证在处理函数完成后立即(同步)重新呈现?

function App() {
  console.log("App rendering...");
  const [counter, setCounter] = React.useState(0);

  // AN EXPENSIVE LOOP TO SLOW DOWN THE RENDER OF 'App'
  
  for (let i = 0; i < 100000; ) {
    for (let j = 0; j < 10000; j++) {}
    i = i + 1;
  }

  return <Child counter={counter} setCounter={setCounter} />;
}

function Child(props) {
  console.log("Child rendering...");
  
  // THIS FUNCTION WILL CALL 'setState'
  // AND WILL ALSO LOG THE CURRENT 'props.counter'
  
  function handleClick() {
    props.setCounter(prevState => prevState + 1);
    console.log(props.counter);
  }

  return (
    <React.Fragment>
      <div>Counter: {props.counter}</div>
      <button onClick={handleClick}>Click</button>
    </React.Fragment>
  );
}

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

【问题讨论】:

    标签: javascript reactjs react-hooks setstate


    【解决方案1】:

    一旦handleClick 完成,是否保证在handleClick 完成运行后立即发生重新渲染?我的意思是立即,例如同步立即

    对于某些事件,包括click,React 保证组件将在另一个事件发生之前重新渲染。 (对于像mousemove 这样的其他事件,情况并非如此。)您可以找到哪些事件是哪些here。当前的术语似乎是 DiscreteEvents 是存在此保证的术语,而 UserBlockingEvents 是不保证的术语。很可能在当前的实现中,这意味着它在事件处理结束时同步完成¹,但我认为保证没有那么具体。

    我从 Dan Abramov on Twitter 那里学到了这一点(他对事件使用了较旧的术语)。


    ¹ 编辑:事实上,在 his answer Joseph D. 中指出 a comment by Dan Abramov 说现在是这样,但也说“这是实现细节,可能会在未来的版本中改变” em>。

    【讨论】:

    • 这是否意味着在实际调用setState 时(在另一个事件发生之前发生的批处理期间)这也包括渲染步骤?所以 setState 由 React 完成的批处理调用就像是同步调用更新状态和重新渲染(如果需要)?
    • 你找到你推文的答案了吗? setState 刷新是保证在触发事件结束之前发生,还是在下一个事件开始之前发生?
    • @AdamThompson - 当您的代码调用 setState 时,不,渲染未完成。之后,当 React 对您的组件(和其他组件)的调用完成时,它就完成了。我的理解是,这发生在 React 处理底层事件的最后,但这是一个实现细节。
    • 很有趣,所以如果您使用setState 更改控制按钮是否禁用的状态,用户可能会单击该按钮两次,但到第二个事件触发状态将保证被更新。在这种情况下,从技术上讲,您是否需要检查事件处理程序中的状态(仅禁用按钮 技术上 不够,但实际上可能会根据当前的实现细节工作,其中状态已刷新并且渲染发生在事件退出之前)。
    • @AdamThompson - 是的。但是考虑到如果 React 的内部结构发生变化以使第二次点击通过时会中断的代码量,我怀疑它会发生,即使在并发模式下也是如此。 :-)
    【解决方案2】:

    是否保证在处理程序完成后立即(同步)重新渲染?

    onClick 是。

    As pointed by Dan Abramov:

    React 批处理在 React 事件处理程序期间完成的所有 setState,并在退出之前应用它们它自己的浏览器事件处理程序。

    【讨论】:

    • 另外值得注意的是,他说“这是实现细节,可能会在未来的版本中改变。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-28
    • 1970-01-01
    • 2020-03-24
    • 2019-09-01
    • 1970-01-01
    相关资源
    最近更新 更多