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

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更新触发了重渲染,才出现了这种“看似自动更新”的效果:

  1. State是引用类型:persons数组里存的是对象的引用,你用person.number = newPerson.number直接修改了内存中的原对象——这个对象和组件渲染时用的是同一个引用。
  2. 其他State更新触发重渲染:修改完对象后你调用了setNewName('')和setTelNumber(''),这两个方法会触发组件重渲染。重渲染时组件读取persons数组的当前值,而你已经修改了数组内对象的属性,所以新值会被渲染出来。
  3. 不用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 14:46:27