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
相关产品推荐
相关产品推荐

