Immer 实战讲解 (2)

recipe 是否有返回值,nextState 的生成过程是不同的:
recipe 没有返回值时:nextState 是根据 recipe 函数内的 draftState 生成的;
recipe 有返回值时:nextState 是根据 recipe 函数的返回值生成的;

let nextState = produce( currentState, (draftState) => { return { x: 2 } } )

此时,nextState 不再是通过 draftState 生成的了,而是通过 recipe 的返回值生成的。

recipe中的this

recipe 函数内部的this指向 draftState ,也就是修改this与修改 recipe 的参数 draftState ,效果是一样的。
注意:此处的 recipe 函数不能是箭头函数,如果是箭头函数,this就无法指向 draftState 了

produce(currentState, function(draft){ // 此处,this 指向 draftState draft === this; // true }) patch补丁功能

通过此功能,可以方便进行详细的代码调试和跟踪,可以知道 recipe 内的做的每次修改,还可以实现时间旅行。

Immer 中,一个 patch 对象是这样的:

interface Patch { op: "replace" | "remove" | "add" // 一次更改的动作类型 path: (string | number)[] // 此属性指从树根到被更改树杈的路径 value?: any // op为 replace、add 时,才有此属性,表示新的赋值 }

语法:

produce( currentState, recipe, // 通过 patchListener 函数,暴露正向和反向的补丁数组 patchListener: (patches: Patch[], inversePatches: Patch[]) => void ) applyPatches(currentState, changes: (patches | inversePatches)[]): nextState

例子:

import produce, { applyPatches } from "immer" let state = { x: 1 } let replaces = []; let inverseReplaces = []; state = produce( state, draft => { draft.x = 2; draft.y = 2; }, (patches, inversePatches) => { replaces = patches.filter(patch => patch.op === 'replace'); inverseReplaces = inversePatches.filter(patch => patch.op === 'replace'); } ) state = produce(state, draft => { draft.x = 3; }) console.log('state1', state); // { x: 3, y: 2 } state = applyPatches(state, replaces); console.log('state2', state); // { x: 2, y: 2 } state = produce(state, draft => { draft.x = 4; }) console.log('state3', state); // { x: 4, y: 2 } state = applyPatches(state, inverseReplaces); console.log('state4', state); // { x: 1, y: 2 }

state.x的值4次打印结果分别是:3、2、4、1,实现了时间旅行,
可以分别打印patches和inversePatches看下,

patches数据如下:

[ { op: "replace", path: ["x"], value: 2 }, { op: "add", path: ["y"], value: 2 }, ]

inversePatches数据如下:

[ { op: "replace", path: ["x"], value: 1 }, { op: "remove", path: ["y"], }, ]

可见,patchListener内部对数据操作做了记录,并分别存储为正向操作记录和反向操作记录,供我们使用。

至此,Immer 的常用功能和 api 我们就介绍完了。

接下来,我们看如何用 Immer ,提高 React 、Redux 项目的开发效率。

用immer优化react项目的探索

首先定义一个state对象,后面的例子使用到变量state或访问this.state时,如无特殊声明,都是指这个state对象

state = { members: [ { name: 'ronffy', age: 30 } ] } 抛出需求

就上面定义的state,我们先抛一个需求出来,好让后面的讲解有的放矢:
members 成员中的第1个成员,年龄增加1岁

优化setState方法 错误示例 this.state.members[0].age++;

只所以有的新手同学会犯这样的错误,很大原因是这样操作实在是太方便了,以至于忘记了操作 State 的规则。

下面看下正确的实现方法

setState的第1种实现方法 const { members } = this.state; this.setState({ members: [ { ...members[0], age: members[0].age + 1, }, ...members.slice(1), ] }) setState的第2种实现方法 this.setState(state => { const { members } = state; return { members: [ { ...members[0], age: members[0].age + 1, }, ...members.slice(1) ] } })

以上2种实现方式,就是setState的两种使用方法,相比大家都不陌生了,所以就不过多说明了,接下来看下,如果用 Immer 解决,会有怎样的烟火?

用immer更新state this.setState(produce(draft => { draft.members[0].age++; }))

是不是瞬间代码量就少了很多,阅读起来舒服了很多,而且更易于阅读了。

优化reducer immer的produce的拓展用法

在开始正式探索之前,我们先来看下 produce 的拓展用法:

例子:

let obj = {}; let producer = produce((draft, arg) => { obj === arg; // true }); let nextState = producer(currentState, obj);

相比 produce 第2种使用方式的例子,多定义了一个obj对象,并将其作为 producer 方法的第2个参数传了进去;可以看到, produce 内的 recipe 回调函数的第2个参数与obj对象是指向同一块内存。
ok,我们在知道了 produce 的这种拓展用法后,看看能够在 Redux 中发挥什么功效?

普通reducer怎样解决上面抛出的需求 const reducer = (state, action) => { switch (action.type) { case 'ADD_AGE': const { members } = state; return { ...state, members: [ { ...members[0], age: members[0].age + 1, }, ...members.slice(1), ] } default: return state } } 集合immer,reducer可以怎样写 const reducer = (state, action) => produce(state, draft => { switch (action.type) { case 'ADD_AGE': draft.members[0].age++; } })

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppjgz.html