当前位置: 首页 > 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.lryc.cn/news/595323.html

相关文章:

  • Ubuntu安装k8s集群入门实践-v1.31
  • HOT100——图篇Leetcode207. 课程表
  • Redis入门教程(一):基本数据类型
  • (LeetCode 每日一题) 1957. 删除字符使字符串变好 (字符串)
  • 17 BTLO 蓝队靶场 Pretium 解题记录
  • 【C++11】哈希表与无序容器:从概念到应用
  • 【Unity基础】Unity中2D和3D项目开发流程对比
  • 用户虚拟地址空间布局架构
  • git_guide
  • 【Git#6】多人协作 企业级开发模型
  • 【面经】实习经历
  • 深入理解 C++ 中的指针与自增表达式:*a++、(*a)++ 和 *++a 的区别解析
  • 破除扫描边界Photoneo MotionCam-3D Color 解锁动态世界新维度
  • 京东疯狂投资具身智能:众擎机器人+千寻智能+逐际动力 | AI早报
  • 2021 RoboCom 世界机器人开发者大赛-本科组(复赛)解题报告 | 珂学家
  • [硬件电路-64]:模拟器件 -二极管在稳压电路中的应用
  • 物流链上的智慧觉醒:Deepoc具身智能如何重塑搬运机器人的“空间思维”
  • 库卡气体保护焊机器人省气的方法
  • Java IO流体系详解:字节流、字符流与NIO/BIO对比及文件拷贝实践
  • 大模型高效适配:软提示调优 Prompt Tuning
  • 【Windows】多标签显示文件夹
  • PLC之间跨区域通讯!无线通讯方案全解析
  • SQL通用增删改查
  • Spring Cache 扩展:Redis 批量操作优化方案与 BatchCache 自定义实现
  • C++中的deque容器
  • vue3实现可视化大屏布局
  • 相机标定(非ROS相机)
  • hard_err错误
  • 【PTA数据结构 | C语言版】哥尼斯堡的“七桥问题”
  • 2000 兆瓦挖矿合作落地,GSP 2031 携 Whitebird 拓展全球合规算力版图