React immer.js使用

React immer.js使用

基本介绍

一个基于react的第三方库,是一个很小的包,能以更方便的方式处理不可变状态(immutable state)。

解决的痛点 :让js对于复杂对象(嵌套较深)的修改变得更加容易。
原理: es6中proxy的概念。

原理

简化了不可变数据结构的处理

可用于需要使用不可变数据结构的任何上下文中。如react状态,react或react reducers或配置管理等。

  • 针对不可变的数据结构能够做到变更检测
    如果对象的引用没有更改,则对象本身也没有更改
    如果没有在draft中对state对象做修改,那么返回值和原对象是一样的,绝对相等
  • 使得克隆成本较低
    原对象中,未更改的属性(树)部分不做复制,在内存中与原旧版本的属性共享属性 (树)。

通常来说,为了不更改原对象、数组或映射的任何属性,但又需要创建新对象并对其属性进行操作的时候 我们通常是对原对象进行深拷贝,然后通过操作拷贝的对象的属性来实现。

解决的痛点

  1. Immer会检测到意外变更并抛出错误。
  2. Immer能避免对不可变对象进行深度更新时,所需要的常规手动拷贝代码的实现。如果没有Immer,对象副本需要在每一级上手工创建其副本,通常通过使用很解构操作(…obj)操作。 当使用Immer时,只需要对 draft对象进行更改,draft对象会先记录用户的修改,然后仅创建有变更的必要的属性副本,不会影响原始对象。
  3. 在使用Immer时,您不需要额外学习专用的api或数据结构,使用普通的JavaScript数据结构并使用常规方式修改数据即可,操作简单且安全。

使用

我们可以利用 produce 函数,它的第一个参数为我们想要操作的初始的状态。 第二个参数是我们传递一个名为 recipe 的函数 该函数自动传入了一个 draft 对象作为参数,我们可以直接修改该 draft 对象。 一旦修改完成,这些修改将被记录下来并用于后续产生下一个状态。 之后,Produce 将负责将上面的变更进行必要的复制,并通对对象进行冻结,防止未来被意外修改。

import produce from 'immer';

const nextState = produce(baseState, (draft) => {
  draft[1].done = true;
  draft.push({ title: 'Tweet about it' });
});

优点

  1. 遵循不可变数据规范,同时使用普通的JavaScript对象、数组、集合和映射。不需要学习新的api或“语法”!
  2. 强类型,没有基于字符串的路径选择器等。 结构共享,仅复制需要的数据部分。
  3. 冻结对象,不会被轻易改变。
  4. 深度更新轻而易举,不需要人工考虑其数据结构会被影响或者遗漏。
  5. 使用简单,能使代码更简洁。
  6. 对JSON补丁的一流支持
  7. 体积小,gzipped 压缩后仅3 kb

应用场景

  1. 用于 React 的 state 的变更。
    React 的 state 本身是不可修改的,当你需要修改它的某个属性然后保存为新的状态的时候,
    使用 immer 可以很方便的获得一个新的 state。
  2. 需要复制一个不可变对象,在不改变原对象的情况下,修改其中的某个值,保存为一个新的对象。
  3. 复制一个不可变的数组,在不改变原数组的情况下,修改其中某个值,保存为新的数组。

类似深拷贝

import produce from 'immer';
const baseState = { a: 1 };
const nextState = produce(baseState, (draft) => {
  draft[1].done = true;
  draft.push({ title: 'Tweet about it' });
});

那为什么不用深拷贝

首先,深拷贝是完全复制,拷贝之后的对象和原对象有不同的堆内存存储空间

const baseState = [{ a: { b: 1 } }, { a: 2 }];
const nextState = JSON.parse(JSON.stringify(baseState));
console.log(nextState === baseState); // false

在react状态共享时,如果有其他组件用到了当前状态,用深拷贝会导致重新渲染,因为状态内容没变但内存地址发生了变化,用shouldComponentUpdate 判断的话,上一次的属性和新的属性是不一样的,所以要重新渲染。但是如果是 immer 的话就可以避免这种情况。

当然,即使是浅拷贝,新旧对象也不再相等,其对象的指针也会改变。 解构赋值是浅拷贝

const baseState = [{ a: { b: 1 } }, { a: 2 }];
const nextState = [...baseState];
console.log(nextState === baseState); // false

再来看immer

import produce from 'immer';
const baseState = [{ a: { b: 1 } }, { a: 2 }];
const nextState = produce(baseState, (draft) => {
  draft[0].a.b = 1;
});
console.log(nextState === baseState); // true

可以看出,经过 immer 处理之后,两个对象竟然是相等的。 这是因为,immer 在处理 draft的时候,如果没有变更,或者变更之后和原来一样就不会改变对象,其对象指针还是同一个。

如果对象有变更时,二者不再相等。

import produce from 'immer';
const baseState = [{ a: { b: 1 } }, { a: 2 }];
const nextState = produce(baseState, (draft) => {
  draft[0].a.b = 2;
});
console.log(nextState === baseState); // false
console.log(nextState, baseState); // 二者的值不一样。

可以理解draft为es6的proxy对象,draft 是个 Proxy 代理对象,对它的读写操作会走到内部定义的 getter/setter 里。
当访问 draft 时,其定义的 getter 会返回一个 Proxy 代理对象。
如果在 draft 中没有值的变更或者变更值和原对象一致,则返回原对象。

proxy

简单总结一下,就是 proxy 是源对象的代理,当你对代理进行读/写的时候,会进入到 handler 的 get, set 或者是 delete 等方法中,从而实现对对象的劫持。通过这种方式相当于获得了一个增强功能版的源对象,

Immer 仅适用于处理不可变对象

官网介绍:that allows you to work with immutable state in a more convenient way.
也就是说,immer 的根本目的是为了处理“不可变对象”而存在的(比如 React 的 state)。

import produce from 'immer';
const baseState = [{ a: { b: 1 } }, { a: 2 }];
const nextState = produce(baseState, (draft) => {
  draft[0].a.b = 2;
});
console.log(nextState); // 输出值: [{ a: { b: 2 } }, { a: 2 }]
console.log(baseState); // 输出值: [{ a: { b: 1 } }, { a: 2 }]

// 直接修改 nextState 的属性值
nextState.a.b = 999;
// 直接修改 baseState 的属性值
baseState.a.b = 999;

可以看到,报错了。 很显然,经过 immer 处理之后的 nextState 修改属性值的时候报错了。 而且,原对象 baseState 修改属性值的时候同样会报错。

  1. 仅复制必要数据(非完全复制对象)
  2. 防止未来被意外修改。

参考链接:
link.

link.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值