如何在React状态中修改已初始化的类实例数组对象
如何高效修改React状态中类实例数组的属性并遵循最佳实践
场景说明
开发React应用时,使用useState存储一组Journey类实例作为状态,该类用于解析数据库数据。偶尔需要在状态初始化完成后修改这些实例的属性,但现有两种方案都存在问题:
初始代码示例
import React, { useEffect, useState } from 'react'; const initialJourneysFromDB = [ { idDB: 1, vehicleDB: "Car", dateDB: "2023-06-01", childrenDB: 2 , adultsDB: 1}, { idDB: 2, vehicleDB: "Bus", dateDB: "2023-06-02", childrenDB: 3 , adultsDB: 3}, { idDB: 1, vehicleDB: "Car", dateDB: "2023-06-05", childrenDB: 1 , adultsDB: 2}, { idDB: 3, vehicleDB: "Jet", dateDB: "2023-06-03", childrenDB: 0 , adultsDB: 2}, ]; // 该类结构复杂,核心作用是解析数据库数据 // - 包含上百个参数,通过对象点标记获取 // - 重命名数据库字段 // - 解析过程中会丢失部分原始数据 // - 部分属性需要初始化后基于全量实例计算得出 class Journey { constructor(params) { this.id = params.idDB; this.vehicle = params.vehicleDB; this.date = params.dateDB; this.passengers = params.childrenDB + params.adultsDB; // 丢失儿童和成人的单独数量 this.pastJourneysForSameVehicle = null; // 初始化后计算的属性 } setPastJourneys(journeys) { this.pastJourneysForSameVehicle = journeys.filter( journey => journey.vehicle === this.vehicle && journey.date < this.date ); } } function App() { const [journeys, setJourneys] = useState([]); useEffect(() => { const parsedJourneyList = initialJourneysFromDB.map(journeyData => { return new Journey(journeyData); }) parsedJourneyList.forEach(journeyData => { journeyData.setPastJourneys(parsedJourneyList); // 初始化后设置关联属性 }) setJourneys(parsedJourneyList) }, []) return ( // ...JSX ); }
现有方案的问题
1. 直接修改对象
直接修改实例属性,违反React状态不可变原则,可能导致组件无法正确更新:
const modifyPropertyDirectly = () => { const updatedJourneys = [...journeys]; const journeyToUpdate = updatedJourneys.find(journey => journey.id === 1) journeyToUpdate.date = "2023-06-04"; setJourneys(updatedJourneys); };
2. 重新创建普通对象
通过展开运算符复制属性创建新对象,会破坏原有的类实例结构,丢失类方法和内部逻辑:
const recreateObjectWithModifiedProperty = () => { const updatedJourneys = journeys.map(journey => { const newObj = {...journey} if (journey.id === 1) { newObj.date = "2023-06-04" } return newObj; }); setJourneys(updatedJourneys); };
符合最佳实践的解决方案
方案一:给Journey类添加不可变更新方法
在类中新增一个方法,返回包含修改属性的新类实例,既保持类结构,又遵循不可变原则:
class Journey { // ... 原有构造函数和方法 // 新增不可变更新方法 update(updates) { // 创建包含当前实例所有属性的对象,合并更新内容 const updatedParams = { idDB: this.id, vehicleDB: this.vehicle, dateDB: this.date, // 注意:如果原始数据库字段已丢失,需要确保这里能构造出正确的参数 childrenDB: 0, // 若原始数据丢失,需根据现有字段反推或提前存储 adultsDB: this.passengers }; Object.assign(updatedParams, updates); const newJourney = new Journey(updatedParams); return newJourney; } } // 使用方式 const updateJourneyImmutably = () => { setJourneys(prevJourneys => { const updatedList = prevJourneys.map(journey => { if (journey.id === 1) { // 返回新实例,传入需要修改的数据库字段 return journey.update({ dateDB: "2023-06-04" }); } return journey; }); // 重新计算所有实例的关联属性 updatedList.forEach(journey => journey.setPastJourneys(updatedList)); return updatedList; }); };
方案二:使用复制构造函数
给Journey类添加支持从现有实例创建新实例的构造逻辑,直接基于旧实例生成新实例并修改属性:
class Journey { constructor(params) { // 如果传入的是Journey实例,直接复制属性 if (params instanceof Journey) { this.id = params.id; this.vehicle = params.vehicle; this.date = params.date; this.passengers = params.passengers; this.pastJourneysForSameVehicle = params.pastJourneysForSameVehicle; } else { // 原有数据库参数解析逻辑 this.id = params.idDB; this.vehicle = params.vehicleDB; this.date = params.dateDB; this.passengers = params.childrenDB + params.adultsDB; this.pastJourneysForSameVehicle = null; } // 支持传入修改的属性 if (params.updates) { Object.assign(this, params.updates); } } // ... 原有setPastJourneys方法 } // 使用方式 const updateWithCopyConstructor = () => { setJourneys(prevJourneys => { const updatedList = prevJourneys.map(journey => { if (journey.id === 1) { // 创建新实例,传入旧实例和要修改的属性 return new Journey({ ...journey, updates: { date: "2023-06-04" } }); } return journey; }); // 重新计算关联属性 updatedList.forEach(journey => journey.setPastJourneys(updatedList)); return updatedList; }); };
方案三:基于现有实例属性重新实例化
如果类的构造逻辑允许,直接提取现有实例的属性,合并修改后创建新实例:
const updateByReinstantiating = () => { setJourneys(prevJourneys => { const updatedList = prevJourneys.map(journey => { if (journey.id === 1) { // 提取当前实例的属性,构造符合构造函数要求的参数 const newParams = { idDB: journey.id, vehicleDB: journey.vehicle, dateDB: "2023-06-04", // 修改目标属性 childrenDB: 0, // 若原始数据丢失,需根据现有字段反推或提前存储 adultsDB: journey.passengers }; return new Journey(newParams); } return journey; }); // 重新计算依赖全量数据的属性 updatedList.forEach(journey => journey.setPastJourneys(updatedList)); return updatedList; }); };
核心原则总结
- 始终遵循React状态不可变原则,绝不直接修改状态中的实例
- 保持
Journey类的实例结构,确保类的方法和内部逻辑可用 - 修改后需重新计算依赖全量数据的属性,保证数据一致性
内容的提问来源于stack exchange,提问作者Andres Aranda
相关产品推荐
相关产品推荐

