当前位置: 首页 > news >正文

React 中使用immer修改state摆脱“不可变”

什么是 Immer

Immer 是一个小型的 JavaScript 库,它让你可以以可变的方式来编写代码,而最终得到的是不可变的数据。想象一下,你可以直接拿起积木修改积木塔,而最后呈现给 React 的是一个全新搭建好的积木塔。这就是 Immer 为我们做的事情。它使用了 Proxy(在现代 JavaScript 环境中)或 ES5 的 Object.defineProperty 来实现这一魔法。 

为什么 React 需要不可变状态

 在深入了解 Immer 之前,我们先搞清楚为什么 React 要求我们不可变地更新状态。在 React 中,当状态发生变化时,React 会通过比较新旧状态来决定是否重新渲染组件。如果我们直接修改状态,React 可能无法检测到状态的变化,从而导致组件不会重新渲染。而不可变更新会创建一个新的状态对象,React 可以很容易地检测到这个变化并正确地重新渲染组件。

不使用不可变更新的问题示例

下面是一个简单的 React 组件示例,展示了不使用不可变更新可能会遇到的问题:

import React, { useState } from 'react';// 定义一个简单的 React 组件
const App = () => {// 使用 useState 钩子来管理状态,初始状态是一个包含 name 属性的对象const [user, setUser] = useState({ name: '张三' });// 定义一个函数来更新用户的名字const updateName = () => {// 直接修改 user 对象,这违反了 React 的不可变更新原则user.name = '李四';// 尝试设置状态,但 React 不会检测到变化,因为引用没有改变setUser(user);};return (<div><p>User name: {user.name}</p><button onClick={updateName}>Update Name</button></div>);
};export default App;

在这个例子中,当我们点击按钮时,user.name 被直接修改了,但 React 不会重新渲染组件,因为 setUser 接收的还是同一个对象引用。 

使用不可变更新的正确做法 

为了让 React 能够正确检测到状态变化,我们需要使用不可变更新。以下是使用不可变更新的示例:

import React, { useState } from 'react';// 定义一个简单的 React 组件
const App = () => {// 使用 useState 钩子来管理状态,初始状态是一个包含 name 属性的对象const [user, setUser] = useState({ name: '张三' });// 定义一个函数来更新用户的名字,使用不可变更新const updateName = () => {// 创建一个新的对象,包含更新后的属性const newUser = { ...user, name: '李四' };// 设置新的状态setUser(newUser);};return (<div><p>User name: {user.name}</p><button onClick={updateName}>Update Name</button></div>);
};export default App;

在这个例子中,我们使用展开运算符 ... 创建了一个新的对象 newUser,并将更新后的 name 属性赋值给它。然后将 newUser 传递给 setUser,这样 React 就能检测到状态的变化并重新渲染组件。

Immer使用

安装 Immer

在开始使用 Immer 之前,我们需要先安装它。可以使用 npm 或 yarn 来安装:

npm install immer
# 或者
yarn add immer
Immer 的基本使用方法
创建一个 draft

Immer 的核心概念是 draft(草稿)。当你使用 Immer 时,它会给你一个可以直接修改的 draft 对象,而不是原始的状态对象。在你完成对 draft 的修改后,Immer 会基于这些修改创建一个新的不可变状态对象。

下面是一个使用 Immer 的简单示例:

import { useState } from "react";
import {produce} from 'immer';
function App() {// 使用 useState 钩子来管理状态,初始状态是一个包含 name 属性的对象const [user, setUser] = useState({ name: '张三' });// 定义一个函数来更新用户的名字,使用 Immer 的 produce 函数const updateName = () => {// 使用 produce 函数创建一个 draft 对象const newUser = produce(draft => {// 直接修改 draft 对象,就像修改普通对象一样draft.name = '李四';});// 设置新的状态setUser(newUser);};return (<><p>User name: {user.name}</p><button onClick={updateName}>Update Name</button></>);
}export default App;

在这个例子中,produce 函数可以接收两个参数:原始状态 user 和一个回调函数。回调函数接收一个 draft 对象,我们可以直接在这个 draft 对象上进行修改。当回调函数执行完毕后,produce 会返回一个新的不可变状态对象 newUser,我们将这个新对象传递给 setUser 来更新状态。

处理数组状态

Immer 特别擅长处理嵌套状态。在 React 应用中,状态往往是嵌套的对象或数组,手动进行不可变更新会变得非常复杂。而使用 Immer,我们可以像处理普通对象一样轻松地修改嵌套状态。

数组对象示例

下面是一个处理嵌套对象状态的示例:

import { useState } from "react";
import { produce } from "immer";
const List = () => {const [list, setList] = useState([{ id: "1", title: "问卷一", isPublished: false },{ id: "2", title: "问卷二", isPublished: true },{ id: "3", title: "问卷三", isPublished: false },{ id: "4", title: "问卷四", isPublished: true },]);const deleteEdit = (id: string) => {//使用immerconst newList = produce((draft) => {return draft.filter(item => item.id != id);});setList(newList);//不可变更新//  const list1 = list.filter(item=>item.id !=id)// setList(list1);};const addEdit = () => {//使用immerconst newList = produce((draft) => {draft.push({id: new Date().toString(),title: "新闻卷" + new Date(),isPublished: false,});});setList(newList);//不可变更新//  setList(//   list.concat({//     id: new Date().toString(),//     title: "问卷五",//     isPublished: false,//   })// );};return (<><h1>问卷调查</h1><div><div><button onClick={addEdit}>新增问卷</button></div>{list.map((item) => {const { id, title, isPublished } = item;return (<div key={id}><strong>{title}</strong>&nbsp; &nbsp;{isPublished ? (<span style={{ color: "green" }}>已发布</span>) : (<span>未发布</span>)}&nbsp; &nbsp;<button onClick={() => deleteEdit(id)}>删除问卷</button></div>);})}</div></>);
};
export default List;

在这个例子中,我们有一个嵌套的数组 state,包含多个对象使用 Immer,我们可以直接在 draft 对象上修改嵌套数组中的元素,而不需要手动创建新的数组和对象。

在 Redux 中使用 Immer 

Immer 也可以和 Redux 一起使用,让 Redux 的 reducer 函数更加简洁和易于维护。在 Redux 中,reducer 函数必须返回一个新的状态对象,而不能直接修改原始状态。使用 Immer,我们可以在 reducer 函数中以可变的方式编写代码。

Redux 示例
import { createStore } from 'redux';
import produce from 'immer';// 定义初始状态
const initialState = {users: [{ id: 1, name: 'John', age: 25 },{ id: 2, name: 'Jane', age: 30 }],selectedUser: null
};// 定义 reducer 函数,使用 Immer 的 produce 函数
const reducer = produce((draft, action) => {switch (action.type) {case 'ADD_USER':// 直接在 draft 对象的 users 数组中添加一个新用户draft.users.push(action.payload);break;case 'UPDATE_USER':// 找到要更新的用户const userToUpdate = draft.users.find(user => user.id === action.payload.id);if (userToUpdate) {// 直接修改用户的属性Object.assign(userToUpdate, action.payload);}break;case 'SELECT_USER':// 直接设置 selectedUser 属性draft.selectedUser = action.payload;break;default:break;}
}, initialState);// 创建 Redux 商店
const store = createStore(reducer);// 订阅商店的变化
store.subscribe(() => {console.log('Current state:', store.getState());
});// 分发一个 ADD_USER 动作
store.dispatch({type: 'ADD_USER',payload: { id: 3, name: 'Bob', age: 35 }
});// 分发一个 UPDATE_USER 动作
store.dispatch({type: 'UPDATE_USER',payload: { id: 1, name: 'Updated John', age: 26 }
});// 分发一个 SELECT_USER 动作
store.dispatch({type: 'SELECT_USER',payload: { id: 2, name: 'Jane', age: 30 }
});

 在这个例子中,我们有一个包含多个用户的状态对象,以及一个 selectedUser 属性。使用 Immer,我们可以在 reducer 函数中直接修改 draft 对象,而不需要手动创建新的对象和数组。

Immer 的优势

代码简洁性

使用 Immer 可以让我们的代码更加简洁和易于理解。在不使用 Immer 的情况下,我们需要手动进行不可变更新,这会导致代码变得冗长和复杂。而使用 Immer,我们可以直接在 draft 对象上进行修改,就像修改普通对象一样,代码变得更加直观。

减少错误

手动进行不可变更新容易出错,特别是在处理嵌套状态时。我们可能会忘记创建新的对象或数组,从而导致状态更新失败。而使用 Immer,我们可以避免这些错误,因为 Immer 会自动处理不可变更新。

提高开发效率

由于 Immer 让我们可以以可变的方式编写代码,我们可以更快地实现状态更新逻辑。我们不需要花费大量的时间来手动创建新的对象和数组,从而提高了开发效率。

更好的代码可维护性

使用 Immer 编写的代码更加易于维护。当我们需要修改状态更新逻辑时,我们只需要在 draft 对象上进行修改,而不需要担心不可变更新的细节。这使得代码的可读性和可维护性都得到了提高。

总结

总结:Immer 是一个非常强大的库,它让我们可以以可变的方式编写代码,而最终得到的是不可变的数据。在 React 中使用 Immer 可以简化状态更新逻辑,提高代码的简洁性、减少错误、提高开发效率和代码的可维护性。无论是处理简单的状态还是复杂的嵌套状态,Immer 都能帮助我们轻松应对

http://www.dtcms.com/a/291098.html

相关文章:

  • 打造自己的 Jar 文件分析工具:类名匹配 + 二进制搜索 + 日志输出全搞定
  • 从一开始的网络攻防(六):php反序列化
  • UART串口
  • 什么是内网穿透?本地内网无公网IP如何实现互联网上远程访问?
  • 每日一题7.21
  • 自动化商品监控:利用淘宝API开发实时价格库存采集接口
  • springdoc-openapi-ui的使用教程
  • 嵌入式开发学习———Linux环境下C语言学习(十二)
  • 【Tools】Ubuntu24.04安装详细教程
  • mobaxteam x11传输界面避坑
  • SAP 邮箱配置
  • C语言运算符优先级“潜规则”
  • 原型与原型链
  • 二维码扫描登录流程详解
  • 【Elasticsearch】settings
  • 解密分账系统:企业资金管理的智能中枢
  • Linux的相关指令
  • 京东商品评论如何获取?API接口实战指南
  • Kali MSF渗透Windows 11电脑
  • Linux_gdb调试器--进程概念
  • Linux初识网络
  • MySQL 核心知识点梳理(3)
  • buntu 22.04 上离线安装Docker 25.0.5(二)
  • 如何升级到macOS Tahoe:全面指南与实用步骤
  • LeetCode 每日一题 2025/7/14-2025/7/20
  • Mysql(存储过程)
  • 图像编辑开源数据项目
  • 了解 ReAct 框架:语言模型中推理与行动的协同
  • 疯狂星期四文案网第14天运营日记
  • DBSCAN聚类算法