You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React使用中遇到setState执行前DOM提前更新的异常问题

React setState前ref获取到更新后DOM的疑问

复现代码

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>
    <!-- react -->
    <script
      crossorigin
      src="https://unpkg.com/react@18/umd/react.development.js"
    ></script>
    <script
      crossorigin
      src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
    ></script>
    <!-- babel -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel">
      class App extends React.Component {
        constructor(props) {
          super(props)
          this.state = { text: "Hello,World" }
          this.titleRef = React.createRef()
        }
        changeText() {
          // Get node information through ref
          // Print output before setState. => `<h2>5555</h2>`
          console.log(this.titleRef.current)
          // {text:"Hello,World"}
          console.log(this.state)           
          this.setState((state) => {
            return { text: "5555" }
          })
        }
        render() {
          const { text } = this.state
          return (
            <div>
              <h2 ref={this.titleRef}>{text}</h2>
              <button
                onClick={() => {
                  this.changeText()
                }}
              >
                change text
              </button>
            </div>
          )
        }
      }
      const root = ReactDOM.createRoot(document.querySelector("#root"))
      root.render(<App />)
    </script>
  </body>
</html>

问题描述

在changeText方法中,原本预期第一个console.log输出为<h2>Hello,World</h2>——因为这行代码在setState之前,此时state数据尚未变更,DOM应该仍为<h2>Hello,World</h2>。但实际输出却是<h2>5555</h2>,DOM的更新似乎先于setState执行,想请教该现象的原因。

原因分析

你遇到的这个情况,核心由两个机制共同导致:

1. React 18严格模式的开发模式双渲染

你使用了React 18的ReactDOM.createRoot API,它默认会开启严格模式。在开发模式下,严格模式会对组件的更新流程执行两次渲染+一次回滚的操作,目的是帮你检测潜在的副作用问题:

  • 第一次调用setState后,React会更新DOM为<h2>5555</h2>,此时titleRef.current会指向这个新创建的DOM节点;
  • 紧接着React会触发回滚,把状态重置为原来的{text: "Hello,World"},DOM也会改回<h2>Hello,World</h2>,但titleRef的引用并没有被重置——它依然指向之前那个已经脱离DOM树的<h2>5555</h2>节点。

2. 浏览器控制台的延迟展开特性

你打印的this.titleRef.current是DOM对象(属于引用类型),浏览器控制台并不会在执行console.log时立即生成对象的快照,而是在你手动点击展开对象的时候,才去读取它当前的实际属性值。那个被回滚后脱离DOM的<h2>5555</h2>节点,它的innerHTML依然是5555,所以你展开时看到的就是这个最终值。

验证方式

可以通过两个测试来验证上述结论:

  • 把打印语句改为console.log(this.titleRef.current.innerHTML):直接打印字符串(值类型),会看到输出的是Hello,World,和你的预期一致;
  • 关闭严格模式:将createRoot替换为React 17的ReactDOM.render,此时开发模式下只会渲染一次,打印的ref就会是<h2>Hello,World</h2>的DOM节点。

内容的提问来源于stack exchange,提问作者Dragoran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 23:47:37