React中两个useState同步变更问题排查及正确初始化咨询
问题原因与解决方案
错误原因
你遇到的问题核心是对象引用共享:
- 虽然初始化时用
[...initialValues]做了数组的浅拷贝,但数组里的每个员工对象还是引用类型,employees和records的初始元素都指向initialValues里的同一个对象实例。 - 在
handleChange函数中,你直接修改了原对象的属性emp[k] = v,因为records里的对象和这个对象是同一个内存引用,所以哪怕没调用setRecords,records的内容也会被同步修改。
正确实现方法
要解决这个问题,需要保证两个state的对象完全独立,同时遵循React状态不可变的原则:
1. 正确初始化state
初始化时要对数组内的对象做拷贝(这里用对象展开语法做浅拷贝足够,因为对象都是单层结构),避免引用共享:
const [employees, setEmployees] = useState([ ...initialValues.map(item => ({...item})), {...emptyRow} ]); const [records, setRecords] = useState(initialValues.map(item => ({...item})));
2. 修改状态时遵循不可变性
handleChange里不能直接修改原对象,要创建新的对象副本:
const handleChange = (e, id) => { const k = e.target.name; const v = e.target.value; const updated = employees.map((emp) => { if (emp.id === id) { // 返回新的对象,不修改原对象 return {...emp, [k]: v}; } return emp; }); setEmployees(updated); };
完整修改后的代码
import { useState } from "react"; import "./App.css"; const initialValues = [ { id: 1, name: "John", position: "Developer", salary: 50000, isUpdated: false, }, { id: 2, name: "Jane", position: "Designer", salary: 60000, isUpdated: false, }, { id: 3, name: "Bob", position: "Manager", salary: 70000, isUpdated: false, }, ]; const emptyRow = { id: 0, name: "", position: "", salary: 0, isUpdated: false, }; const headings = ["Name", "Position", "Salary", "Action"]; const Employee = ({ employee, handleChange, handleClick }) => ( <tr key={employee.id}> <td> <input type="text" id="name" name="name" onChange={(e) => handleChange(e, employee.id)} value={employee.name} disabled={employee.id !== 0} /> </td> <td> <input type="text" id="position" name="position" onChange={(e) => handleChange(e, employee.id)} value={employee.position} disabled={employee.id !== 0} /> </td> <td> <input type="number" id="salary" name="salary" onChange={(e) => handleChange(e, employee.id)} value={employee.salary ? employee.salary : ""} /> </td> <td> <button onClick={() => handleClick(employee.id)}>{employee.id !== 0 ? "Save" : "Add"}</button> </td> </tr> ); function App() { // 初始化时对对象做拷贝,避免引用共享 const [employees, setEmployees] = useState([ ...initialValues.map(item => ({...item})), {...emptyRow} ]); const [records, setRecords] = useState(initialValues.map(item => ({...item}))); const handleClick = (id) => { const foundEmp = employees.find((x) => x.id === id); const foundRec = records.find((x) => x.id === id); console.log(`------- foundEmp :`, JSON.stringify(foundEmp, null, 2)); console.log(`------- foundRec :`, JSON.stringify(foundRec, null, 2)); // 这里可以添加保存逻辑,比如更新records if (id === 0) { // 添加新员工到records const newEmp = {...foundEmp, id: Date.now()}; setRecords([...records, newEmp]); // 重置空行 setEmployees([...employees.map(item => item.id === 0 ? {...emptyRow} : item)]); } else { // 保存现有员工到records setRecords(records.map(item => item.id === id ? {...foundEmp} : item)); } }; const handleChange = (e, id) => { const k = e.target.name; const v = e.target.value; const updated = employees.map((emp) => { if (emp.id === id) { // 返回新对象,不修改原对象 return {...emp, [k]: v}; } return emp; }); setEmployees(updated); }; return ( <div className="App"> <form onSubmit={(e) => e.preventDefault()}> <table width="100%" data-testid="table"> <tbody> <tr> {headings?.map((h, i) => ( <th key={i}>{h}</th> ))} </tr> {employees.map((employee) => ( <Employee employee={employee} handleChange={handleChange} handleClick={handleClick} /> ))} </tbody> </table> </form> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Zoe L
相关产品推荐
相关产品推荐

