不可变数据 - Immer.js 改造 reducer

immer 官方文档中写✍️:

基本思想是,使用 Immer,会将所有更改应用到临时 draft ,它是 currentState 的代理。一旦完成了所有的 mutations ,Immer 将根据对 draft state 的 mutations 生成 nextState。这意味着可以通过简单地修改数据来与数据交互,同时保留不可变数据的所有好处。

使用 Immer 就像拥有一个私人助理。助手拿一封信(当前状态)并给您一份副本(草稿)以记录更改。完成后,助手将接受您的草稿并为您生成真正不变的最终字母(下一个状态)。

核心实现是利用 ES6 的proxy,几乎以最小的成本实现了JavaScript的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对 JS 不可变数据结构的需求。

为什么要追求不可变数据

  1. 性能优化 :不可变数据结构使得在检测数据变化时更加高效。在调和阶段(Reconciliation) React 使用 Virtual DOM 来比较前后两次渲染的虚拟DOM树,找出需要更新的部分,然后只更新这些部分,而不是重新渲染整个DOM。使用不可变数据可以让React在比较时更容易确定哪些数据发生了变化,从而提高性能。

  2. 可预测性:不可变数据使得数据的变化变得可预测。在React中,组件的状态应该是不可变的,这意味着给定一组数据,可以确保它在一段时间内不会改变,这有助于在组件中跟踪数据的状态和变化。

  3. 减少副作用:不可变数据有助于减少副作用。

  4. 更容易调试:不可变数据使得调试更加容易。

  5. 跟踪数据变化:React中的一些生命周期方法和钩子函数依赖于不可变数据,以便正确地触发组件的重新渲染。

基本概念

  • currentState:被操作对象的最初状态
  • draftState: 根据currentState生成的草稿、是currentState的代理、对draftState所有的修改都被记录并用于生成nextState。在此过程中,currentState不受影响
  • nextState: 根据draftState生成的最终状态
  • produce: 用于生成nextState或者producer的函数
  • Producer: 通过produce生成,用于生产nextState,每次执行相同的操作
  • recipe:用于操作draftState的函数

实践

安装

js 复制代码
npm install immer

参考版本号:9.0.19

这里需要配合 @reduxjs/toolkit 使用

ts 复制代码
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { nanoid } from "nanoid";
import produce from "immer";

export type TodoItemType = {
  id: string;
  title: string;
  completed: boolean;
};

const INIT_STATE: TodoItemType[] = [
  { id: nanoid(5), title: "吃饭", completed: false },
  { id: nanoid(5), title: "睡觉", completed: false },
];

export const todoListSlice = createSlice({
  name: "todoList",
  initialState: INIT_STATE,
  reducers: {
    //...
  },
});

export default todoListSlice.reducer;

这是一个 todo 的基础框架。

addTodo

现在需要向 INIT_STATE 中添加数据。实现 addTodo 函数。

ts 复制代码
addTodo(state: TodoItemType[], action: PayloadAction<TodoItemType>) {
  return [
    action.payload,
    ...state,
  ];
},

使用 immer 来实现

ts 复制代码
addTodo: produce(
  (draft: TodoItemType[], action: PayloadAction<TodoItemType>) => {
    draft.unshift(action.payload);
  }
),

removeTodo

移除数组中的某个元素,保证数据不可变,可以使用 array filter

ts 复制代码
removeTodo(state: TodoItemType[], action: PayloadAction<{ id: string }>) {
  const { id: removeId } = action.payload;
  return state.filter((todo) => todo.id !== removeId);
},

使用 immer 来实现

ts 复制代码
removeTodo: produce(
  (draft: TodoItemType[], action: PayloadAction<{ id: string }>) => {
    const { id: removeId } = action.payload;
    const index = draft.findIndex((i) => i.id === removeId);
    draft.splice(index, 1)
  }
),

toggleCompleted

对数组的中的某个元素进行直接的修改

ts 复制代码
toggleCompleted(
  state: TodoItemType[],
  action: PayloadAction<{ id: string }>
) {
  const { id: toggleId } = action.payload;
  return state.map((todo) => {
    const { id, completed } = todo;
    if (id !== toggleId) return todo;
    return {
      ...todo,
      completed: !completed,
    };
  });
},

使用 immer 来实现

ts 复制代码
toggleCompleted: produce(
  (draft: TodoItemType[], action: PayloadAction<{ id: string }>) => {
    const { id: toggleId } = action.payload;
    const todo = draft.find((i) => i.id === toggleId);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }
),

也就是说,基本上可以像操作数组那样方便,但是又可以保证数据的不可变性。

源码

github.com/immerjs/imm...

源码分析文章可阅读:不可变数据实现-Immer.js

相关推荐
Java后端的Ai之路3 小时前
07React_Props验证器
人工智能·python·react.js·架构·fastapi
Aolith5 小时前
从收藏到错题本,我在 React 全栈项目里实现了一套复习系统
mongodb·react.js·全栈
一木 之林6 小时前
Agent智能体学习总结:从一次调用到模型自主规划、工具调用与 ReAct 循环的项目实战-6/6
前端·人工智能·学习·react.js
song5011 天前
React Native for OpenHarmony 实战:三方库 react-native-parallax-scroll-view 的鸿蒙化适配指南
分布式·react native·react.js·transformer·harmonyos
song5011 天前
React Native for OpenHarmony 实战:三方库 react-native-ratings 的鸿蒙化适配指南
javascript·react native·react.js·分类·harmonyos
晓得迷路了1 天前
栗子前端技术周刊第 149 期 - Vite+ 1.0、Preact 11、Next.js 16.4...
前端·javascript·react.js
song5011 天前
React Native for OpenHarmony 实战:三方库 react-native-popup-menu 的鸿蒙化适配指南
css·深度学习·react native·react.js·transformer·harmonyos
haluhalu.1 天前
Agent = LLM + 上下文 + 工具(含观察 / 动作空间、ReAct 循环)
前端·javascript·人工智能·python·react.js·机器学习·个人开发
weixin_448119942 天前
Datawhale Easy Data × AI:构建知识与记忆驱动的 Agent笔记8
人工智能·笔记·react.js
EatFan2 天前
React 项目踩坑实录:useEffect 闭包陷阱、Context 性能陷阱与 React 18 升级避坑排查手册
前端·javascript·react.js·react·react hooks·useeffect·闭包陷阱