0

0

在 React 中安全地更新数组中对象的属性值

霞舞

霞舞

发布时间:2025-10-27 11:16:01

|

880人浏览过

|

来源于php中文网

原创

在 React 中安全地更新数组中对象的属性值

react 应用中,直接修改状态中的数组或对象属性会导致“cannot assign to read only property”错误,且无法触发 ui 更新。本文将详细讲解如何在 react 中正确地更新数组中对象的属性值,核心在于遵循 react 的不可变性原则,通过创建数据副本并更新状态,确保组件能够响应式地重新渲染。

理解 React 状态管理与不可变性

在 React 中,组件的 UI 是由其状态(State)驱动的。当状态发生变化时,React 会重新渲染组件以反映这些变化。然而,React 的状态更新机制依赖于引用比较:它会检查新的状态引用是否与旧的状态引用不同。如果直接修改现有状态对象或数组的内部属性,其引用本身并未改变,React 就无法检测到变化,因此不会触发重新渲染。此外,在严格模式(Strict Mode)下,或者当状态数据被冻结(例如,通过 Object.freeze())时,直接赋值还会导致“Cannot assign to read only property”错误。

因此,在 React 中更新状态时,必须遵循“不可变性”原则,即不直接修改原始状态数据,而是创建新的数据副本,然后在新副本上进行修改,最后用这个新副本替换旧状态。

错误的更新方式及其原因

考虑以下场景,我们有一个包含多个对象的数据数组,并希望通过点击按钮来改变其中一个对象的 Actions 属性:

export const Data = [
  {
    FileID: 1,
    Name: 'david',
    Date: '10/02/2022',
    hour: '21:00',
    Actions: true,
  },
  {
    FileID: 2,
    Name: 'Ben',
    Date: '10/04/2022',
    hour: '22:00',
    Actions: true,
  },
  {
    FileID: 3,
    Name: 'Alex',
    Date: '22/06/2022',
    hour: '21:00',
    Actions: true,
  },
];

// 错误的尝试
<button disabled={!Data[0].Actions} onClick={() => {
    Data[0].Actions = false; // 直接修改原始数据
}} className="bg-red-600 mt-2 p-3 rounded-2xl text-sm text-white">
  Click
</button>

上述代码尝试直接修改 Data 数组中第一个对象的 Actions 属性。这会导致两个问题:

  1. “Cannot assign to read only property”错误:如果 Data 数组或其内部对象被冻结,或者在某些 JavaScript 环境下,直接修改会抛出此错误。
  2. UI 不会更新:即使没有抛出错误,由于 Data 数组的引用没有改变,React 也不会认为状态已更新,因此不会重新渲染组件来反映 Actions 值的变化。

正确的更新方式:利用 useState 和不可变性

在 React 函数组件中,我们使用 useState Hook 来管理状态。要正确更新数组中对象的属性,需要执行以下步骤:

WEBGM游戏金币虚拟货币交易源代码
WEBGM游戏金币虚拟货币交易源代码

WEBGM2.0版对原程序进行了大量的更新和调整,在安全性和实用性上均有重大突破.栏目介绍:本站公告、最新动态、网游资讯、游戏公略、市场观察、我想买、我想卖、点卡购买、火爆论坛特色功能:完美的前台界面设计以及人性化的管理后台,让您管理方便修改方便;前台介绍:网站的主导行栏都采用flash设计,美观大方;首页右侧客服联系方式都采用后台控制,修改方便;首页中部图片也采用动态数据,在后台可以随意更换图片

下载
  1. 将数据存储在组件状态中:使用 useState 初始化你的数据数组。
  2. 创建数组的浅拷贝:当需要修改数组中的某个元素时,首先创建整个数组的一个新副本。
  3. 定位并修改目标对象:在新副本中找到需要修改的对象,并更新其属性。
  4. 使用状态更新函数:调用 useState 返回的更新函数,传入修改后的新数组副本。

下面是一个完整的示例,演示了如何通过点击按钮来更新数组中指定对象的 Actions(或 disabled)属性:

import React, { useState } from 'react';

// 初始数据
const initialData = [
  {
    FileID: 1,
    Name: 'David',
    Date: '10/02/2022',
    hour: '21:00',
    Actions: true, // 假设Actions代表是否可操作
  },
  {
    FileID: 2,
    Name: 'Ben',
    Date: '10/04/2022',
    hour: '22:00',
    Actions: true,
  },
  {
    FileID: 3,
    Name: 'Alex',
    Date: '22/06/2022',
    hour: '21:00',
    Actions: true,
  },
];

function DataUpdater() {
  // 使用 useState 管理数据数组
  const [dataList, setDataList] = useState(initialData);

  /**
   * 处理按钮点击事件,更新指定 FileID 的对象的 Actions 属性
   * @param {number} fileId 要更新的对象的 FileID
   */
  const handleUpdateAction = (fileId) => {
    // 1. 创建 dataList 的一个浅拷贝
    const updatedDataList = [...dataList];

    // 2. 查找要更新的对象的索引
    const index = updatedDataList.findIndex(item => item.FileID === fileId);

    // 3. 如果找到了对象,则更新其 Actions 属性
    if (index !== -1) {
      // 在拷贝的数组中修改对象属性。
      // 注意:这里直接修改了拷贝数组中的对象,这对于浅层对象是可行的。
      // 如果对象内部还有嵌套对象,且需要深度不可变,则需要进一步拷贝内部对象。
      updatedDataList[index].Actions = false;

      // 4. 使用 setDataList 更新状态,触发组件重新渲染
      setDataList(updatedDataList);
    }
  };

  return (
    <div className="p-4">
      <h2 className="text-xl font-bold mb-4">数据列表</h2>
      {dataList.map((item) => (
        <div key={item.FileID} className="mb-2 p-3 border rounded-md flex items-center justify-between">
          <span>
            FileID: {item.FileID}, Name: {item.Name}, Actions: {item.Actions ? 'Enabled' : 'Disabled'}
          </span>
          <button
            disabled={!item.Actions} // 按钮的 disabled 状态取决于 Actions 属性
            onClick={() => handleUpdateAction(item.FileID)}
            className={`p-2 rounded-md text-sm text-white
              ${item.Actions ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-400 cursor-not-allowed'}
            `}
          >
            {item.Actions ? '禁用此项' : '已禁用'}
          </button>
        </div>
      ))}
    </div>
  );
}

export default DataUpdater;

代码解析:

  1. useState(initialData):dataList 变量持有当前的数据数组,setDataList 是用于更新这个数组的函数。
  2. [...dataList]:这是 JavaScript 的扩展运算符,用于创建一个 dataList 数组的浅拷贝。这样,我们就可以在新数组上进行操作,而不会直接修改原始的 dataList 状态。
  3. findIndex():用于找到需要修改的对象的索引。
  4. updatedDataList[index].Actions = false;:直接修改了拷贝数组中特定对象的 Actions 属性。由于 updatedDataList 是一个新数组,即使它内部的对象引用与原数组中的对象相同,但由于 updatedDataList 本身是一个新引用,setDataList 会检测到变化并触发重新渲染。
  5. setDataList(updatedDataList):将修改后的新数组设置为组件的新状态。React 会检测到 dataList 的引用已经改变,从而重新渲染组件,反映出 Actions 属性的最新值。

注意事项与最佳实践

  • 浅拷贝与深拷贝:上述示例使用了数组的浅拷贝 ([...dataList])。这意味着数组中的对象本身仍然是原始对象的引用。如果你的对象内部还有嵌套的对象或数组,并且你需要修改这些嵌套结构,那么你可能需要进行深拷贝(例如使用 JSON.parse(JSON.stringify(obj)) 或专门的深拷贝库如 Lodash 的 cloneDeep)或者在修改嵌套对象时也遵循不可变性原则,逐层创建副本。
    • 例如,如果 item 对象内部有 details: { description: '...' },并且你要修改 description,则需要这样操作:
      updatedDataList[index] = {
        ...updatedDataList[index], // 拷贝原对象的所有属性
        Actions: false, // 更新 Actions 属性
        details: { // 也要拷贝 details 对象
          ...updatedDataList[index].details,
          description: 'new description' // 更新嵌套属性
        }
      };
  • 性能考虑:对于非常大的数组或频繁的状态更新,频繁地创建数组和对象的副本可能会带来一定的性能开销。在这种情况下,可以考虑使用专门的不可变数据结构库,如 Immer.js,它允许你以“可变”的方式编写代码,但在底层会自动处理不可变更新,从而简化代码并优化性能。
  • 状态提升:在更复杂的应用中,如果多个组件需要访问或修改相同的数据,你可能需要将状态提升到它们的共同父组件,并通过 props 传递数据和更新函数。

总结

在 React 中更新数组中对象的属性,核心在于理解并实践不可变性原则。避免直接修改原始状态,而是通过创建数据副本,在新副本上进行修改,然后使用 useState 的更新函数来替换旧状态。这种模式不仅能避免“read-only”错误,更能确保 React 能够正确地检测到状态变化并触发 UI 重新渲染,从而构建出稳定、可预测且易于维护的应用程序。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

457

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

547

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

335

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

82

2025.09.10

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

Go语言中的运算符有哪些
Go语言中的运算符有哪些

Go语言中的运算符有:1、加法运算符;2、减法运算符;3、乘法运算符;4、除法运算符;5、取余运算符;6、比较运算符;7、位运算符;8、按位与运算符;9、按位或运算符;10、按位异或运算符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

241

2024.02.23

php三元运算符用法
php三元运算符用法

本专题整合了php三元运算符相关教程,阅读专题下面的文章了解更多详细内容。

150

2025.10.17

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

549

2023.12.01

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 6万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号