React Hooks中为何使用扩展运算符?状态更新渲染异常解析
嗨,我来帮你把这个问题理清楚~
问题场景
- 预期行为:每次点击按钮后,页面渲染的计数器数值加1
- 实际行为:页面上的计数器数值没有增加,但内部状态的数值已经更新(可通过alert消息查看),且该问题并非每次都会出现
你的代码
import React, { useState } from "react"; import "./App.css"; function App(props) { const [all, setAll] = useState([{ name: "PHP", votes: 1 }]); function changeAll() { const newAll = all; alert(newAll === all); // alert true newAll[0].votes++; setAll(newAll); alert(all[0].votes); } return ( <> <div className="voteCount">{all[0].votes}</div> <div className="lanugageName">{all[0].name}</div> <button onClick={changeAll}>Click Here</button> </> ); } export default App;
已尝试的有效方案
将const newAll = all;修改为const newAll = [...all];后,页面能正常更新计数器,此时alert(newAll === all)显示false
问题解答
1. 为什么需要使用扩展运算符?
React判断是否需要重新渲染组件的核心逻辑是对新旧状态进行浅比较:只有当状态的引用发生变化时,才会触发组件重渲染。
当你直接写const newAll = all时,只是把原数组的引用地址复制给了newAll——也就是说,newAll和all指向的是内存中同一个数组对象。哪怕你修改了数组内部元素的属性(比如votes++),数组本身的引用并没有改变。调用setAll(newAll)时,React发现新旧状态的引用是同一个,就会认为“状态没变化”,因此不会触发组件重渲染,页面也就不会更新。
而扩展运算符[...all]的作用是创建一个全新的数组实例:这个新数组和原数组的内容完全一致,但它在内存中拥有独立的地址。此时调用setAll(newAll),React对比新旧状态的引用,发现是不同的对象,就会触发组件重渲染,页面上的计数器数值也就随之更新了。
2. 使用扩展运算符后newAll === all为何为false?
JavaScript中,对象(包括数组)的严格相等(===)比较的是引用地址,而不是内容。[...all]创建了一个新数组,它和原数组all的内容一样,但内存地址完全不同,所以newAll === all的结果是false;而直接赋值const newAll = all时,两个变量指向同一个数组,所以比较结果是true。
补充:这里的
[...all]是浅拷贝,数组内部的对象(比如{name: "PHP", votes: 1})还是原来的引用,但在这个场景下,只要数组的引用变了,React就会触发重渲染,所以足够解决问题。如果需要完全独立的对象副本,还需要对数组内的对象也进行浅拷贝或深拷贝,但这里不需要。
内容的提问来源于stack exchange,提问作者cikatomo

