创见博客
Immer.js为什么对Redux这么重要
七崽爱吃小饼干2026/01/15阅读 0专栏 React

Immer.js 在 Redux 生态中为何如此重要

Immer 成为 Redux 生态「标配级」依赖的核心原因,本质是:Immer 完美解决了 Redux 的核心痛点,而且做到了「无痛兼容、极低学习成本、无侵入性」,是 Redux 生态里「降本增效」的最优解。

一、先明确 Redux 的「核心铁律」与「原生痛点」

想要理解 Immer 的价值,必须先记住 Redux 的一个不可违背的核心规则:

Redux 要求:State 是只读的(Read-only)、不可变的(Immutable),永远不能直接修改 state 对象/数组,必须返回一个「全新的状态对象」。

为什么 Redux 要强制「不可变更新」?

这是 Redux 底层的运行根基,原因有2个:

  1. Redux 的数据变更追踪、组件重新渲染、redux-devtools 时间旅行调试、状态回溯等核心功能,全都依赖「浅比较(shallow equality)」 —— 浅比较只对比引用地址,不对比对象内部的值;
  2. 如果直接修改原 state(比如 state.name = 'new'),原对象的引用地址不变,Redux 就无法感知到状态发生了变化,会导致:组件不重新渲染、调试工具无法记录变更、状态无法回滚,整个 Redux 体系失效。

Redux 原生「不可变更新」的痛点(噩梦级体验)

Redux 要求返回新对象,但原生 JS 实现「不可变更新」的写法极其繁琐、冗余、易错,尤其是面对「嵌套层级较深」的复杂 state 时,堪称灾难。

原生写法的痛点示例(真实开发场景)

假设 Redux 的 state 是多层嵌套结构:

javascript
const state = {
  user: {
    info: { name: "张三", age: 20 },
    address: { province: "北京", city: "朝阳" }
  },
  todos: [{ id: 1, done: false }, { id: 2, done: true }]
}

需求:把 todos[0].done 修改为 true,同时修改 user.info.name 为「李四」 👉 原生 Redux 必须这么写(手动深拷贝+返回新对象):

javascript
// Redux reducer 原生不可变更新写法
return {
  ...state, // 浅拷贝第一层
  user: {
    ...state.user, // 浅拷贝第二层 user
    info: {
      ...state.user.info, // 浅拷贝第三层 info
      name: "李四" // 修改目标属性
    }
  },
  todos: state.todos.map((item, idx) => {
    if (idx === 0) return { ...item, done: true } // 数组项也要返回新对象
    return item
  })
}

这种写法的问题肉眼可见:

  • 层级越深,「扩展运算符嵌套」越多,代码量爆炸,可读性极差;
  • 手动拷贝容易遗漏层级,一不小心就写出「直接修改原state」的错误代码;
  • 数组操作(增删改)需要用 map/filter/concat 等返回新数组的方法,不能用 push/splice 等修改原数组的方法,心智负担极大。

二、Immer.js 是什么?核心原理一句话讲透

Immer 的核心定位

Immer 是一个极小的 JS 库(体积≈2KB),核心只暴露一个「生产函数」:produce,它是 Redux 官方强烈推荐的不可变更新工具,也是 Redux Toolkit(RTK) 的内置核心依赖。

Immer 的核心原理

Immer = 可变的写法 + 不可变的结果 英文总结:Write mutable code, get immutable data

Immer 原理的完整拆解

Immer 内部做了3件核心事,没有任何复杂逻辑,完全是「语法糖级别的优化」:

  1. 你调用 produce(原状态, 回调函数) 时,Immer 会基于「原状态」创建一个临时的代理对象(draft),这个 draft 是原状态的「镜像副本」;
  2. 你在回调函数里,可以像写普通 JS 一样「直接修改」这个 draft 对象 —— 比如 draft.user.info.name = '李四'、draft.todos[0].done = true,所有修改都是「可变写法」,没有任何嵌套和拷贝;
  3. Immer 内部会自动追踪你对 draft 的所有修改,当回调执行完毕后,Immer 会基于你的修改,返回一个「全新的、不可变的状态对象」,原状态完全不会被修改,完美符合 Redux 的不可变要求。

核心关键点:你修改的是「draft副本」,不是原state;最终返回的新对象,引用地址一定和原state不同,Redux 能完美感知到状态变更。


三、Immer 如何解决 Redux 痛点?

还是上面那个需求:修改 todos[0].done = true + 修改 user.info.name = '李四',用 Immer 实现 Redux reducer:

javascript
import { produce } from "immer"

// Immer 版本的 reducer
const reducer = (state, action) => {
  return produce(state, (draft) => {
    // ✅ 完全是「可变写法」,想怎么改就怎么改,无嵌套、无拷贝
    draft.user.info.name = "李四"
    draft.todos[0].done = true
  })
}

对比原生写法,Immer 带来的核心收益

  1. 彻底简化代码:多层嵌套的复杂更新,一行代码搞定,代码量减少 80% 以上,可读性拉满;
  2. 消除心智负担:不用再记「扩展运算符、map/filter、concat」等不可变技巧,不用再担心「漏拷贝层级」;
  3. 极低学习成本:只要会写普通 JS 的「可变修改」,就会用 Immer,0 学习成本;
  4. 零错误率:再也不会写出「直接修改原state」的错误代码,彻底规避 Redux 最常见的 bug;
  5. 完全兼容:返回的结果和原生写法的「不可变新对象」完全一致,Redux 感知不到任何差异,无缝集成。

四、Immer 在 Redux 生态中的「核心地位」

Immer 不是「可选插件」,而是 Redux 生态从 v4 开始的事实标准,核心原因有 3 个,优先级从高到低:

1. Redux 官方「最高级别的认可」:深度绑定,内置集成

  • Redux 核心团队(Dan Abramov 等人)在 Redux 文档中,将 Immer 列为「不可变更新的首选方案」,明确推荐所有开发者使用;

  • Redux Toolkit (RTK) —— Redux 官方推出的「现代 Redux 最佳实践封装」,是目前 Redux 开发的唯一推荐方式,而 RTK 内部直接内置了 Immer,所有的 createSlice/createReducer 都默认基于 Immer 实现,你甚至不需要手动导入 produce,直接写「可变代码」即可。

    举个 RTK 的真实开发示例(日常开发的标准写法):

    javascript
    import { createSlice } from '@reduxjs/toolkit'
    
    const userSlice = createSlice({
      name: 'user',
      initialState: { /* 初始嵌套状态 */ },
      reducers: {
        updateUserInfo: (state, action) => {
          // ✅ RTK 内置 Immer,直接修改 state(实际是 draft)即可,无需 produce
          state.user.info.name = action.payload.name
          state.todos[0].done = true
        }
      }
    })
    

    这是 Redux 官方钦定的「最优写法」,而这一切的核心就是 Immer。

2. 完美契合 Redux 生态的「核心诉求」:无侵入、无副作用、轻量

Redux 生态的核心诉求是「简洁、可维护、无黑魔法」,而 Immer 完全满足:

  • 无侵入:Immer 只在 reducer 内部做处理,不修改 Redux 的任何核心逻辑,不影响中间件、devtools 等生态工具;
  • 无副作用:原 state 永远不会被修改,返回的新 state 完全符合不可变规范,不会引入任何潜在 bug;
  • 极致轻量:体积仅 2KB 左右,引入后对项目打包体积几乎无影响;
  • 性能优秀:Immer 的代理追踪机制是「惰性的」,只追踪你修改的属性,性能和手动不可变更新几乎持平,甚至在复杂嵌套场景下更快。

3. 解决了 Redux 最大的「入门门槛」和「团队协作痛点」

Redux 被很多开发者吐槽「难学、难用」,核心原因就是「不可变更新」的写法太反人类。

  • 对新手:Immer 让新手可以快速上手 Redux,不用先死记硬背「不可变更新」的各种技巧,降低了 Redux 的学习曲线;
  • 对团队:统一了状态更新的写法,避免了团队中有人写原生嵌套拷贝、有人写 Lodash 的 cloneDeep、有人直接修改原 state 的混乱情况,提升了团队协作效率。

五、补充:Immer 的两个核心使用原则

Immer 虽然好用,但有两个「极简原则」必须遵守,否则会写出错误代码,这也是 Redux 官方强调的点:

原则 1:在 Immer 的回调函数中,要么「修改 draft」,要么「return 新值」,二选一,不要混用

正确写法1(推荐):修改 draft,不 return 任何值

javascript
produce(state, draft => {
  draft.name = '李四' // ✅ 正确,只修改 draft
})

正确写法2:不修改 draft,直接 return 新值

javascript
produce(state, draft => {
  return { ...draft, name: '李四' } // ✅ 正确,return 新值
})

错误写法:既修改 draft,又 return 新值(Immer 会忽略你的 draft 修改,只认 return 的值)

javascript
produce(state, draft => {
  draft.name = '李四'
  return { age: 20 } // ❌ 错误,最终只会返回 { age:20 },draft 修改无效
})

原则 2:不要在回调函数中「手动 return draft」

draft 是 Immer 的「临时代理对象」,不是最终的不可变对象,手动 return draft 会破坏不可变性,导致 Redux 无法感知状态变化:

javascript
produce(state, draft => {
  draft.name = '李四'
  return draft // ❌ 错误!draft 是代理对象,不是最终的新状态
})

正确做法:修改 draft 后,什么都不要 return,Immer 会自动帮你生成并返回正确的新状态。


总结(核心知识点提炼,必记)

  1. Redux 的核心铁律是 State 不可变,原生实现不可变更新的痛点是「嵌套深、代码繁、易出错」;
  2. Immer 的核心价值是:允许你用「可变的写法」,生成「不可变的结果」,完美适配 Redux 的规则;
  3. Immer 的原理极简:创建 draft 代理对象 → 追踪修改 → 生成全新不可变状态,无黑魔法;
  4. Immer 在 Redux 生态中「不可或缺」的核心原因:
    • ✅ 官方强推+RTK内置,是现代 Redux 的标配;
    • ✅ 彻底解决 Redux 最大的写法痛点,降本增效;
    • ✅ 无侵入、轻量、兼容所有生态,无任何副作用;
  5. 一句话概括:没有 Immer,现代 Redux 开发的体验会倒退一大步,Immer 不是 Redux 的「锦上添花」,而是「雪中送炭」。
评论
0/100