成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

immer.js 實(shí)戰(zhàn)講解文檔

zhiwei / 964人閱讀

摘要:無(wú)奈網(wǎng)絡(luò)上完善的文檔實(shí)在太少,所以自己寫了一份,本篇文章以貼近實(shí)戰(zhàn)的思路和流程,對(duì)進(jìn)行了全面的講解。這使得成為了真正的不可變數(shù)據(jù)。的使用非常靈活,多多思考,相信你還可以發(fā)現(xiàn)更多其他的妙用參考文檔官方文檔

文章在 github 開源, 歡迎 Fork 、Star
前言

Immer 是 mobx 的作者寫的一個(gè) immutable 庫(kù),核心實(shí)現(xiàn)是利用 ES6 的 proxy,幾乎以最小的成本實(shí)現(xiàn)了 js 的不可變數(shù)據(jù)結(jié)構(gòu),簡(jiǎn)單易用、體量小巧、設(shè)計(jì)巧妙,滿足了我們對(duì)JS不可變數(shù)據(jù)結(jié)構(gòu)的需求。
無(wú)奈網(wǎng)絡(luò)上完善的文檔實(shí)在太少,所以自己寫了一份,本篇文章以貼近實(shí)戰(zhàn)的思路和流程,對(duì) Immer 進(jìn)行了全面的講解。

數(shù)據(jù)處理存在的問(wèn)題

先定義一個(gè)初始對(duì)象,供后面例子使用:
首先定義一個(gè)currentState對(duì)象,后面的例子使用到變量currentState時(shí),如無(wú)特殊聲明,都是指這個(gè)currentState對(duì)象

let currentState = {
  p: {
    x: [2],
  },
}

哪些情況會(huì)一不小心修改原始對(duì)象?

// Q1
let o1 = currentState;
o1.p = 1; // currentState 被修改了
o1.p.x = 1; // currentState 被修改了

// Q2
fn(currentState); // currentState 被修改了
function fn(o) {
  o.p1 = 1;
  return o;
};

// Q3
let o3 = {
  ...currentState
};
o3.p.x = 1; // currentState 被修改了

// Q4
let o4 = currentState;
o4.p.x.push(1); // currentState 被修改了
解決引用類型對(duì)象被修改的辦法

深度拷貝,但是深拷貝的成本較高,會(huì)影響性能;

ImmutableJS,非常棒的一個(gè)不可變數(shù)據(jù)結(jié)構(gòu)的庫(kù),可以解決上面的問(wèn)題,But,跟 Immer 比起來(lái),ImmutableJS 有兩個(gè)較大的不足:

需要使用者學(xué)習(xí)它的數(shù)據(jù)結(jié)構(gòu)操作方式,沒有 Immer 提供的使用原生對(duì)象的操作方式簡(jiǎn)單、易用;

它的操作結(jié)果需要通過(guò)toJS方法才能得到原生對(duì)象,這使得在操作一個(gè)對(duì)象的時(shí)候,時(shí)刻要注意操作的是原生對(duì)象還是 ImmutableJS 的返回結(jié)果,稍不注意,就會(huì)產(chǎn)生意想不到的 bug。

看來(lái)目前已知的解決方案,我們都不甚滿意,那么 Immer 又有什么高明之處呢?

immer功能介紹 安裝immer

欲善其事必先利其器,安裝 Immer 是當(dāng)前第一要?jiǎng)?wù)

npm i --save immer
immer如何fix掉那些不爽的問(wèn)題

Fix Q1、Q3

import produce from "immer";
let o1 = produce(currentState, draft => {
  draft.p.x = 1;
})

Fix Q2

import produce from "immer";
fn(currentState);
function fn(o) {
  return produce(o, draft => {
    draft.p1 = 1;
  })
};

Fix Q4

import produce from "immer";
let o4 = produce(currentState, draft => {
  draft.p.x.push(1);
})

是不是使用非常簡(jiǎn)單,通過(guò)小試牛刀,我們簡(jiǎn)單的了解了 Immer ,下面將對(duì) Immer 的常用 api 分別進(jìn)行介紹。

概念說(shuō)明

Immer 涉及概念不多,在此將涉及到的概念先行羅列出來(lái),閱讀本文章過(guò)程中遇到不明白的概念,可以隨時(shí)來(lái)此處查閱。

currentState
被操作對(duì)象的最初狀態(tài)

draftState
根據(jù) currentState 生成的草稿狀態(tài),它是 currentState 的代理,對(duì) draftState 所做的任何修改都將被記錄并用于生成 nextState 。在此過(guò)程中,currentState 將不受影響

nextState
根據(jù) draftState 生成的最終狀態(tài)

produce 生產(chǎn)
用來(lái)生成 nextState 或 producer 的函數(shù)

producer 生產(chǎn)者
通過(guò) produce 生成,用來(lái)生產(chǎn) nextState ,每次執(zhí)行相同的操作

recipe 生產(chǎn)機(jī)器
用來(lái)操作 draftState 的函數(shù)

常用api介紹

使用 Immer 前,請(qǐng)確認(rèn)將immer包引入到模塊中

import produce from "immer"

or

import { produce } from "immer"

這兩種引用方式,produce 是完全相同的

produce

備注:出現(xiàn)PatchListener先行跳過(guò),后面章節(jié)會(huì)做介紹

第1種使用方式:

語(yǔ)法:
produce(currentState, recipe: (draftState) => void | draftState, ?PatchListener): nextState

例子1:

let nextState = produce(currentState, (draft) => {

})

currentState === nextState; // true

例子2:

let currentState = {
  a: [],
  p: {
    x: 1
  }
}

let nextState = produce(currentState, (draft) => {
  draft.a.push(2);
})

currentState.a === nextState.a; // false
currentState.p === nextState.p; // true

由此可見,對(duì) draftState 的修改都會(huì)反應(yīng)到 nextState 上,而 Immer 使用的結(jié)構(gòu)是共享的,nextState 在結(jié)構(gòu)上又與 currentState 共享未修改的部分,共享效果如圖(借用的一篇 Immutable 文章中的動(dòng)圖,侵刪):

自動(dòng)凍結(jié)功能

Immer 還在內(nèi)部做了一件很巧妙的事情,那就是通過(guò) produce 生成的 nextState 是被凍結(jié)(freeze)的,(Immer 內(nèi)部使用Object.freeze方法,只凍結(jié) nextState 跟 currentState 相比修改的部分),這樣,當(dāng)直接修改 nextState 時(shí),將會(huì)報(bào)錯(cuò)。
這使得 nextState 成為了真正的不可變數(shù)據(jù)。

例子:

let nextState = produce(currentState, (draft) => {
  draft.p.x.push(2);
})

currentState === nextState; // true
第2種使用方式

利用高階函數(shù)的特點(diǎn),提前生成一個(gè)生產(chǎn)者 producer

語(yǔ)法:
produce(recipe: (draftState) => void | draftState, ?PatchListener)(currentState): nextState

例子:

let producer = produce((draft) => {
  draft.x = 2
});
let nextState = producer(currentState);
recipe的返回值

recipe 是否有返回值,nextState 的生成過(guò)程是不同的:
recipe 沒有返回值時(shí):nextState 是根據(jù) recipe 函數(shù)內(nèi)的 draftState 生成的;
recipe 有返回值時(shí):nextState 是根據(jù) recipe 函數(shù)的返回值生成的;

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

此時(shí),nextState 不再是通過(guò) draftState 生成的了,而是通過(guò) recipe 的返回值生成的。

recipe中的this

recipe 函數(shù)內(nèi)部的this指向 draftState ,也就是修改this與修改 recipe 的參數(shù) draftState ,效果是一樣的。
注意:此處的 recipe 函數(shù)不能是箭頭函數(shù),如果是箭頭函數(shù),this就無(wú)法指向 draftState 了

produce(currentState, function(draft){
  // 此處,this 指向 draftState
  draft === this; // true
})
patch補(bǔ)丁功能

通過(guò)此功能,可以方便進(jìn)行詳細(xì)的代碼調(diào)試和跟蹤,可以知道 recipe 內(nèi)的做的每次修改,還可以實(shí)現(xiàn)時(shí)間旅行。

Immer 中,一個(gè) patch 對(duì)象是這樣的:

interface Patch {
  op: "replace" | "remove" | "add" // 一次更改的動(dòng)作類型
  path: (string | number)[] // 此屬性指從樹根到被更改樹杈的路徑
  value?: any // op為 replace、add 時(shí),才有此屬性,表示新的賦值
}

語(yǔ)法:

produce(
  currentState, 
  recipe,
  // 通過(guò) patchListener 函數(shù),暴露正向和反向的補(bǔ)丁數(shù)組
  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次打印結(jié)果分別是:3、2、4、1,實(shí)現(xiàn)了時(shí)間旅行,
可以分別打印patchesinversePatches看下,

patches數(shù)據(jù)如下:

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

inversePatches數(shù)據(jù)如下:

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

可見,patchListener內(nèi)部對(duì)數(shù)據(jù)操作做了記錄,并分別存儲(chǔ)為正向操作記錄和反向操作記錄,供我們使用。

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

接下來(lái),我們看如何用 Immer ,提高 React 、Redux 項(xiàng)目的開發(fā)效率。

用immer優(yōu)化react項(xiàng)目的探索

首先定義一個(gè)state對(duì)象,后面的例子使用到變量state或訪問(wèn)this.state時(shí),如無(wú)特殊聲明,都是指這個(gè)state對(duì)象

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

就上面定義的state,我們先拋一個(gè)需求出來(lái),好讓后面的講解有的放矢:
members 成員中的第1個(gè)成員,年齡增加1歲

優(yōu)化setState方法 錯(cuò)誤示例
this.state.members[0].age++;

只所以有的新手同學(xué)會(huì)犯這樣的錯(cuò)誤,很大原因是這樣操作實(shí)在是太方便了,以至于忘記了操作 State 的規(guī)則。

下面看下正確的實(shí)現(xiàn)方法

setState的第1種實(shí)現(xiàn)方法
const { members } = this.state;
this.setState({
  members: [
    {
      ...members[0],
      age: members[0].age + 1,
    },
    ...members.slice(1),
  ]
})
setState的第2種實(shí)現(xiàn)方法
this.setState(state => {
  const { members } = state;
  return {
    members: [
      {
        ...members[0],
        age: members[0].age + 1,
      },
      ...members.slice(1)
    ]
  }
})

以上2種實(shí)現(xiàn)方式,就是setState的兩種使用方法,相比大家都不陌生了,所以就不過(guò)多說(shuō)明了,接下來(lái)看下,如果用 Immer 解決,會(huì)有怎樣的煙火?

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

是不是瞬間代碼量就少了很多,閱讀起來(lái)舒服了很多,而且更易于閱讀了。

優(yōu)化reducer immer的produce的拓展用法

在開始正式探索之前,我們先來(lái)看下 produce 第2種使用方式的拓展用法:

例子:

let obj = {};

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

相比 produce 第2種使用方式的例子,多定義了一個(gè)obj對(duì)象,并將其作為 producer 方法的第2個(gè)參數(shù)傳了進(jìn)去;可以看到, produce 內(nèi)的 recipe 回調(diào)函數(shù)的第2個(gè)參數(shù)與obj對(duì)象是指向同一塊內(nèi)存。
ok,我們?cè)谥懒?produce 的這種拓展用法后,看看能夠在 Redux 中發(fā)揮什么功效?

普通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++;
  }
})

可以看到,通過(guò) produce ,我們的代碼量已經(jīng)精簡(jiǎn)了很多;
不過(guò)仔細(xì)觀察不難發(fā)現(xiàn),利用 produce 能夠先制造出 producer 的特點(diǎn),代碼還能更優(yōu)雅:

const reducer = produce((draft, action) => {
  switch (action.type) {
    case "ADD_AGE":
      draft.members[0].age++;
  }
})

好了,至此,Immer 優(yōu)化 reducer 的方法也講解完畢。

Immer 的使用非常靈活,多多思考,相信你還可以發(fā)現(xiàn) Immer 更多其他的妙用!

參考文檔

官方文檔

Introducing Immer: Immutability the easy way

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/99761.html

相關(guān)文章

  • immer.js 簡(jiǎn)介及源碼解析

    摘要:例如維護(hù)一份在內(nèi)部,來(lái)判斷是否有變化,下面這個(gè)例子就是一個(gè)構(gòu)造函數(shù),如果將它的實(shí)例傳入對(duì)象作為第一個(gè)參數(shù),就能夠后面的處理對(duì)象中使用其中的方法上面這個(gè)構(gòu)造函數(shù)相比源代碼省略了很多判斷的部分。 showImg(https://segmentfault.com/img/bV27Dy?w=1400&h=544); 博客鏈接:下一代狀態(tài)管理工具 immer 簡(jiǎn)介及源碼解析 JS 里面的變量類...

    Profeel 評(píng)論0 收藏0
  • Immer.js簡(jiǎn)析

    摘要:所以整個(gè)過(guò)程只涉及三個(gè)輸入狀態(tài),中間狀態(tài),輸出狀態(tài)關(guān)鍵是是如何生成,如何應(yīng)用修改,如何生成最終的。至此基本把上的模式解析完畢。結(jié)束實(shí)現(xiàn)還是相當(dāng)巧妙的,以后可以在狀態(tài)管理上使用一下。 開始 在函數(shù)式編程中,Immutable這個(gè)特性是相當(dāng)重要的,但是在Javascript中很明顯是沒辦法從語(yǔ)言層面提供支持,但是還有其他庫(kù)(例如:Immutable.js)可以提供給開發(fā)者用上這樣的特性,所...

    Aceyclee 評(píng)論0 收藏0
  • Immer.js簡(jiǎn)析

    摘要:所以整個(gè)過(guò)程只涉及三個(gè)輸入狀態(tài),中間狀態(tài),輸出狀態(tài)關(guān)鍵是是如何生成,如何應(yīng)用修改,如何生成最終的。至此基本把上的模式解析完畢。結(jié)束實(shí)現(xiàn)還是相當(dāng)巧妙的,以后可以在狀態(tài)管理上使用一下。 開始 在函數(shù)式編程中,Immutable這個(gè)特性是相當(dāng)重要的,但是在Javascript中很明顯是沒辦法從語(yǔ)言層面提供支持,但是還有其他庫(kù)(例如:Immutable.js)可以提供給開發(fā)者用上這樣的特性,所...

    dackel 評(píng)論0 收藏0
  • 精讀《源碼學(xué)習(xí)》

    摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個(gè)技巧,并利用實(shí)例說(shuō)明了源碼學(xué)習(xí)過(guò)程中可以學(xué)到許多周邊知識(shí),都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請(qǐng)點(diǎn)擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...

    aboutU 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

zhiwei

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<