React State Hook未调用setState却自动被重新赋值的原因咨询
问题描述
我定义了一个React State Hook:const [persons, setPersons] = useState([]),目标是发送PUT请求后重新渲染persons数组。代码能正常运行,但我不理解——没有调用setPersons()方法,persons数组却自动被重新赋值并更新了视图。
相关代码片段:
if(window.confirm(`${newPerson.name} is already added to the phonebook, replace the old number with a new one?`)) { console.log('update person:',newPerson) personsService.updatePerson(newPerson) .then(response => console.log(`${response.name}'s number has been updated`)) // update persons list locally persons.map(person=>{ person.id === newPerson.id ? person.number = newPerson.number : person // 为什么这段代码不用setPersons也能生效? }) // 如果不用这段map代码,数组就不会自动更新
完整代码:
import { useState, useEffect } from 'react' import Filter from './components/Filter' import PersonForm from './components/PersonForm' import Persons from './components/Persons' import personsService from './services/persons' const App = () => { const url = 'http://localhost:3001/persons' const [persons, setPersons] = useState([]) // useEffect在所有组件渲染完成后执行 // 这就是为什么console.log("Execute the other codes...")在console.log("fetching data from the server...")之前执行 // 尽管代码顺序相反 useEffect(()=>{ console.log("fetching data from the server...") personsService.getPersons() .then(response=>{ console.log("Data have been fetched successfully!") console.log("Data: ", response) console.log("Update Persons state...") setPersons(response) }) },[]) console.log("Execute the other codes...") console.log("Initial state of persons state is: ",persons) const [newName, setNewName] = useState('') const [telNumber, setTelNumber] = useState('') const [filter, setFilter] = useState('') // 事件处理器 const onChangeHandler = (event) =>{ setNewName(event.target.value) } const deleteHandler = (id,name) =>{ console.log('Auaaa',id) if(window.confirm(`Delete ${name}?`)) { personsService.deletePerson(id) const arrayAfterDeletion = persons.filter(person=>person.id !== id) setPersons(arrayAfterDeletion) } } const telChangeHandler = (event) =>{ setTelNumber(event.target.value) } const filterChangeHandler= (event) =>{ setFilter(event.target.value) } const onClickHandler = (event)=>{ event.preventDefault() const newPerson = {id:newName, name:newName, number:telNumber} // 检查联系人列表是否已存在同名联系人 let containsTheSamePerson = false persons.forEach(person=>{ // 这里比较对象没意义(因为id不同) if(person.name === newPerson.name) { containsTheSamePerson = true } }) if(!containsTheSamePerson) { const newPersonList = persons.concat(newPerson) //console.log('old list', persons) //console.log('new list',newPersonList); setPersons(newPersonList) // 向服务器添加新联系人 personsService.addNewPerson(newPerson) .then(response => console.log(`${response.name} has been added to database`)) setNewName('') setTelNumber('') }else{ // alert(`${newPerson.name} is already added to the phonebook`) if(window.confirm(`${newPerson.name} is already added to the phonebook, replace the old number with a new one?`)) { console.log('update person:',newPerson) personsService.updatePerson(newPerson) .then(response => console.log(`${response.name}'s number has been updated`)) // 本地更新联系人列表 persons.map(person=>{ person.id === newPerson.id ? person.number = newPerson.number : person // 为什么这段代码不用setPersons也能生效? }) setNewName('') setTelNumber('') } } } return ( <div> <h2>Phonebook</h2> <Filter value = {filter} onChangeHandler = {filterChangeHandler}/> <h3>Add a new</h3> <PersonForm nameValue={newName} nameChangeHandler={onChangeHandler} numValue={telNumber} numChangeHandler={telChangeHandler} buttonHandler={onClickHandler}/> <h2>Numbers</h2> <Persons list = {persons} filter = {filter} onClickDelete={deleteHandler}/> </div> ) } export default App
原因分析
这是因为你直接修改了React State中对象的属性,再加上后续的其他State更新触发了重渲染,才出现了这种“看似自动更新”的效果:
- State是引用类型:
persons数组里存的是对象的引用,你用person.number = newPerson.number直接修改了内存中的原对象——这个对象和组件渲染时用的是同一个引用。 - 其他State更新触发重渲染:修改完对象后你调用了
setNewName('')和setTelNumber(''),这两个方法会触发组件重渲染。重渲染时组件读取persons数组的当前值,而你已经修改了数组内对象的属性,所以新值会被渲染出来。 - 不用map就不更新的原因:如果没有这段map代码,你没修改
persons数组内的对象,重渲染时还是读取原来的属性值,自然不会有变化。
这种写法的问题
虽然这次碰巧生效,但这是违反React设计原则的危险写法:
- React依赖State的引用变化追踪更新,直接修改State内部值本身不会触发React的更新机制(这次是靠其他State更新顺带触发的重渲染)。
- 可能导致不可预测的bug:比如后续如果没有其他State更新,你的修改就不会被渲染;或者在并发渲染模式下出现状态不一致。
正确写法
遵循不可变更新原则,创建新的数组/对象来更新State:
// 替换原来的map代码 const updatedPersons = persons.map(person => person.id === newPerson.id ? { ...person, number: newPerson.number } // 创建新对象,不修改原对象 : person ) setPersons(updatedPersons) // 调用setPersons更新State
这样做能明确触发React的更新机制,确保视图和State保持一致,避免潜在的状态问题。
内容的提问来源于stack exchange,提问作者JavaInsel
相关产品推荐
相关产品推荐

