为何React组件用array.push更新状态不重渲染,展开运算符却可行?
React状态更新不触发组件重渲染的原因分析
问题现象
调用setInfoArray传入通过push修改的数组时,Form组件不重渲染。提交表单后打印infoArray发现内容已更新,但组件并未重新渲染。
无效写法代码:
import React, { useState } from 'react' import './../styles/Form.css' const Form = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [infoArray, setInfoArray] = useState([]); //this function will update infoArray const addToArray = (e) => { e.preventDefault(); const newArray = infoArray; newArray.push({ email: email, password: password }) console.log("newArray:", newArray); setInfoArray(newArray); console.log(infoArray) } return ( <> <form action=""> <div> <label htmlFor="email" className='formLabel'>Email</label> </div> <input type="email" name="email" id="email" value={email} onChange={(e) => { setEmail(e.target.value) }} placeholder='enter email' required autoComplete='off' /> <div> <label htmlFor="password" className='formLabel'>password</label> </div> <input type="password" name="password" value={password} onChange={(e) => { setPassword(e.target.value) }} id="password" placeholder='enter password' required /> <button type="submit" className='submitBtn' onClick={addToArray}>Login</button> </form> {/* display the previous entries */} <div> {infoArray.map((item, index) => { return ( <div className='entry' key={index}> <span>{item.email}</span> <span>{item.password}</span> </div> ) })} </div> </> ) } export default Form
有效写法代码:
const addToArray = (e) => { e.preventDefault(); const newArray = [...infoArray,{email:email,password:password}]; setInfoArray(newArray) }
原因解析
React的状态更新机制依赖引用类型的引用变化来判断是否需要触发组件重渲染:
- 第一种写法中,
const newArray = infoArray只是把原状态数组的引用赋值给了新变量,本质上newArray和infoArray指向内存中的同一个数组。调用push修改数组内容后,数组的引用并没有改变,此时调用setInfoArray(newArray)传入的还是原来的引用。React对比更新前后的状态,发现引用完全相同,就会判定状态没有发生变化,因此不会触发组件重渲染。 - 第二种写法使用扩展运算符
[...infoArray, {email:email,password:password}]创建了一个全新的数组,这个新数组在内存中有独立的引用。调用setInfoArray传入新数组后,React检测到状态的引用发生了变化,就会判定状态已更新,进而触发组件重渲染,页面也就会同步显示新的数组内容。
另外需要注意,console.log(infoArray)在第一种写法中可能会打印出修改后的数组,这是因为JavaScript的引用特性——虽然状态还没更新,但原数组已经被修改了,控制台打印的是数组的实时引用值,而非调用console.log时的快照。
内容的提问来源于stack exchange,提问作者Apoorva Pendse
相关产品推荐
相关产品推荐

