Less code, more Imutable - Immer 介绍 + 源码解读

Immer是一个由mobx作者编写的库,用于简化处理immutable数据。它利用Proxy元编程,允许直接修改draft对象,自动产生新的state。本文介绍了immer的使用方式,原理,如结构共享,以及源码解读,特别是`produce`、`proxy`相关函数。此外,还讨论了immer在某些场景下的性能问题及解决方案,并与Immutable.js、seamless-immutable.js进行了性能对比,展示immer在性能上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Less code, more Imutable - Immer 介绍 + 源码解读

介绍


immer 是 mobX 的作者编写的简化操作 immutable 数据的库。immer 通过使用 Proxy 元编程,使得我们能以一种更简单清晰的方式去处理 immutable 特性的数据。

例子


最直接的好处就是使得我们的代码变得清晰,比如在编写 redux reducer 时,我们需要保证返回新的 state 对象,如果使用深克隆在性能上会带来损耗,如果使用 … 运算符,在面对复杂结构时,则会使得代码变得复杂。


通过 immer 我们可以使用一种清晰易懂的方式编写类似的代码。

import produce from "immer"

const current = [
    {
   
        todo: "Learn typescript",
        done: true
    },
    {
   
        todo: "Try immer",
        done: false
    }
];

// 不使用 immer

const next = [
	...current.map((todo, index) => index === 1 ? {
   ...todo, done: true} : todo),
  {
   todo: "Tweet about it"},
]


// 在使用了 immer 后

const next = produce(current, draft => {
   
    draft.push({
    todo: "Tweet about it", done: false });
    draft[1].done = true;
});

**

reducer

**
实际上 immer 经常被配合 Redux 的 reducer 一起使用,可以大大简化我们编写 reducer 的代码逻辑

// Redux reducer
// Shortened, based on: https://github.com/reactjs/redux/blob/master/examples/shopping-cart/src/reducers/products.js
const reducer = (state = {
   }, action) => {
   
    switch (action.type) {
   
        case RECEIVE_PRODUCTS:
            return {
   
                ...state,
                ...action.products.reduce((obj, product) => {
   
                    obj[product.id] = product
                    return obj
                }, {
   })
            }
        default:
            return state
    }
}

// 使用 immer

import produce from "immer"

const reducer = (state = {
   }, action) => produce(state, (draft) => {
   
    switch (action.type) {
   
        case RECEIVE_PRODUCTS:
            action.products.forEach(product => {
   
                draft[product.id] = product
            })
    }
}, {
   })

**

使用


immer 只有一个核心函数 produce ,第一个参数是我们想要改变的 current 对象,第二个参数是一个函数,这个函数接受一个 draft 对象,而这个 draft 对象,其实就是 produce 内部使用 Proxy 包装后的 current 对象。

我们在函数中通过 draft 修改 current 上的数据,immer 通过 Proxy 拦截,收集我们作出的修改,最终根据这些修改来计算返回一个新的 state 对象。原来 state 对象上的所有数据则保持原样。

在这里插入图片描述

**

原理

**
我们传入的 current 会在 immer 内部使用 Proxy 进行包装,而 draft 就是包装后的 current 对象,我们修改 draft 上的属性时,immer 内部会拦截我们的修改操作,使得变动不会反应到原来的 current 对象上,最后返回一个产生变化的新的 next 对象。

结构共享


不同于深克隆的是,immer 返回的数据是 结构共享 的 。也就是说 current 和 next 共享未改变的部分,减少了不必要的克隆操作带来的额外性能问题。
**
image.png



由于蓝色部分并未发生变动,所以在结构上进行共享。注意,结构共享只是一种内部优化手段,从语义上我们可以看作返回了一个全新的对象。

const current = {
   
    name: 'state',
    a: {
   
        name: 'a',
        b: {
    name: 'b' },
        c: {
   
            name: 'c',
            d: {
    name: 'd' },
            e: {
    name: 'e' },
        }
    }
}

// 如果我们手动的写

const next = {
   
  ...current,
  a: {
   
  	...crrent.a,
    c: {
   
    	...current.c,
      name: 'new c',
    },
  },
};

// 使用 immer

const next = produce(current, draft => {
   
    draft.a.c.name = 'new c';
});

console.log(next === current); // => false
console.log(next.a === current.a); // => false
console.log
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值