亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Immer 功能最佳實(shí)踐示例教程

 更新時(shí)間:2022年10月24日 14:29:42   作者:三年沒洗澡  
這篇文章主要為大家介紹了Immer功能最佳實(shí)踐示例教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

一、前言

Immer  是 mobx 的作者寫的一個(gè) immutable 庫,核心實(shí)現(xiàn)是利用 ES6 的 proxy,幾乎以最小的成本實(shí)現(xiàn)了 js 的不可變數(shù)據(jù)結(jié)構(gòu),簡單易用、體量小巧、設(shè)計(jì)巧妙,滿足了我們對(duì) JS 不可變數(shù)據(jù)結(jié)構(gòu)的需求。

二、學(xué)習(xí)前提

閱讀這篇文章需要以下知識(shí)儲(chǔ)備:

  • JavaScript 基礎(chǔ)語法
  • es6 基礎(chǔ)語法
  • node、npm 基礎(chǔ)知識(shí)

三、歷史背景

在 js 中,處理數(shù)據(jù)一直存在一個(gè)問題:

拷貝一個(gè)值的時(shí)候,如果這個(gè)值是引用類型(比如對(duì)象、數(shù)組),直接賦值給另一個(gè)變量的時(shí)候,會(huì)把值的引用也拷貝過去,在修改新變量的過程中,舊的變量也會(huì)被一起修改掉。

要解決這個(gè)問題,通常我們不會(huì)直接賦值,而是會(huì)選擇使用深拷貝,比如JSON.parse(JSON.stringify()),再比如 lodash 為我們提供的 cloneDeep 方法……

但是,深拷貝并不是十全十美的。

這個(gè)時(shí)候,immer 誕生了!

四、immer 功能介紹

基本思想是,使用 Immer,會(huì)將所有更改應(yīng)用到臨時(shí)  draft,它是  currentState  的代理。一旦你完成了所有的  mutations,Immer 將根據(jù)對(duì)  draft state  的  mutations  生成 nextState。這意味著你可以通過簡單地修改數(shù)據(jù)來與數(shù)據(jù)交互,同時(shí)保留不可變數(shù)據(jù)的所有好處。

一個(gè)簡單的比較示例

const baseState = [
  {
    title: 'Learn TypeScript',
    done: true,
  },
  {
    title: 'Try Immer',
    done: false,
  },
];

假設(shè)我們有上述基本狀態(tài),我們需要更新第二個(gè) todo,并添加第三個(gè)。但是,我們不想改變?cè)嫉?baseState,我們也想避免深度克隆以保留第一個(gè) todo

不使用 Immer

如果沒有 Immer,我們將不得不小心地淺拷貝每層受我們更改影響的 state 結(jié)構(gòu)

const nextState = [...baseState]; // 淺拷貝數(shù)組
nextState[1] = {
  // 替換第一層元素
  ...nextState[1], // 淺拷貝第一層元素
  done: true, // 期望的更新
};
// 因?yàn)?nextState 是新拷貝的, 所以使用 push 方法是安全的,
// 但是在未來的任意時(shí)間做相同的事情會(huì)違反不變性原則并且導(dǎo)致 bug!
nextState.push({ title: 'Tweet about it' });

使用 Immer

使用 Immer,這個(gè)過程更加簡單。我們可以利用  produce  函數(shù),它將我們要更改的 state 作為第一個(gè)參數(shù),對(duì)于第二個(gè)參數(shù),我們傳遞一個(gè)名為 recipe 的函數(shù),該函數(shù)傳遞一個(gè)  draft  參數(shù),我們可以對(duì)其應(yīng)用直接的  mutations。一旦  recipe  執(zhí)行完成,這些  mutations  被記錄并用于產(chǎn)生下一個(gè)狀態(tài)。 produce  將負(fù)責(zé)所有必要的復(fù)制,并通過凍結(jié)數(shù)據(jù)來防止未來的意外修改。

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

使用 Immer 就像擁有一個(gè)私人助理。助手拿一封信(當(dāng)前狀態(tài))并給您一份副本(草稿)以記錄更改。完成后,助手將接受您的草稿并為您生成真正不變的最終信件(下一個(gè)狀態(tài))。

第二個(gè)示例

如果有一個(gè)層級(jí)很深的對(duì)象,你在使用 redux 的時(shí)候,想在 reducer 中修改它的某個(gè)屬性,但是根據(jù) reduce 的原則,我們不能直接修改 state,而是必須返回一個(gè)新的 state

不使用 Immer

const someReducer = (state, action) => {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        third: {
          ...state.first.second.third,
          value: action,
        },
      },
    },
  };
};

使用 Immer

const someReducer = (state, action) => {
  state.first.second.third.value = action;
};

好處

  • 遵循不可變數(shù)據(jù)范式,同時(shí)使用普通的 JavaScript 對(duì)象、數(shù)組、Sets 和 Maps。無需學(xué)習(xí)新的 API 或 "mutations patterns"!
  • 強(qiáng)類型,無基于字符串的路徑選擇器等
  • 開箱即用的結(jié)構(gòu)共享
  • 開箱即用的對(duì)象凍結(jié)
  • 深度更新輕而易舉
  • 樣板代碼減少。更少的噪音,更簡潔的代碼

更新模式

在 Immer 之前,使用不可變數(shù)據(jù)意味著學(xué)習(xí)所有不可變的更新模式。

為了幫助“忘記”這些模式,這里概述了如何利用內(nèi)置 JavaScript API 來更新對(duì)象和集合

更新對(duì)象

import produce from 'immer';
const todosObj = {
  id1: { done: false, body: 'Take out the trash' },
  id2: { done: false, body: 'Check Email' },
};
// 添加
const addedTodosObj = produce(todosObj, draft => {
  draft['id3'] = { done: false, body: 'Buy bananas' };
});
// 刪除
const deletedTodosObj = produce(todosObj, draft => {
  delete draft['id1'];
});
// 更新
const updatedTodosObj = produce(todosObj, draft => {
  draft['id1'].done = true;
});

更新數(shù)組

import produce from 'immer';
const todosArray = [
  { id: 'id1', done: false, body: 'Take out the trash' },
  { id: 'id2', done: false, body: 'Check Email' },
];
// 添加
const addedTodosArray = produce(todosArray, draft => {
  draft.push({ id: 'id3', done: false, body: 'Buy bananas' });
});
// 索引刪除
const deletedTodosArray = produce(todosArray, draft => {
  draft.splice(3 /*索引 */, 1);
});
// 索引更新
const updatedTodosArray = produce(todosArray, draft => {
  draft[3].done = true;
});
// 索引插入
const updatedTodosArray = produce(todosArray, draft => {
  draft.splice(3, 0, { id: 'id3', done: false, body: 'Buy bananas' });
});
// 刪除最后一個(gè)元素
const updatedTodosArray = produce(todosArray, draft => {
  draft.pop();
});
// 刪除第一個(gè)元素
const updatedTodosArray = produce(todosArray, draft => {
  draft.shift();
});
// 數(shù)組開頭添加元素
const addedTodosArray = produce(todosArray, draft => {
  draft.unshift({ id: 'id3', done: false, body: 'Buy bananas' });
});
// 根據(jù) id 刪除
const deletedTodosArray = produce(todosArray, draft => {
  const index = draft.findIndex(todo => todo.id === 'id1');
  if (index !== -1) {
    draft.splice(index, 1);
  }
});
// 根據(jù) id 更新
const updatedTodosArray = produce(todosArray, draft => {
  const index = draft.findIndex(todo => todo.id === 'id1');
  if (index !== -1) {
    draft[index].done = true;
  }
});
// 過濾
const updatedTodosArray = produce(todosArray, draft => {
  // 過濾器實(shí)際上會(huì)返回一個(gè)不可變的狀態(tài),但是如果過濾器不是處于對(duì)象的頂層,這個(gè)依然很有用
  return draft.filter(todo => todo.done);
});

嵌套數(shù)據(jù)結(jié)構(gòu)

import produce from 'immer';
// 復(fù)雜數(shù)據(jù)結(jié)構(gòu)例子
const store = {
  users: new Map([
    [
      '17',
      {
        name: 'Michel',
        todos: [{ title: 'Get coffee', done: false }],
      },
    ],
  ]),
};
// 深度更新
const nextStore = produce(store, draft => {
  draft.users.get('17').todos[0].done = true;
});
// 過濾
const nextStore = produce(store, draft => {
  const user = draft.users.get('17');
  user.todos = user.todos.filter(todo => todo.done);
});

異步 producers & createDraft

允許從 recipe 返回 Promise 對(duì)象?;蛘呤褂?async / await。這對(duì)于長時(shí)間運(yùn)行的進(jìn)程非常有用,只有在 Promise 鏈解析后才生成新對(duì)象

注意,如果 producer 是異步的,produce 本身也會(huì)返回一個(gè) promise。

例子:

import produce from 'immer';
const user = { name: 'michel', todos: [] };
const loadedUser = await produce(user, async draft => {
  draft.todos = await (await fetch('http://host/' + draft.name)).json();
});

請(qǐng)注意,draft 不應(yīng)從異步程序中“泄露”并存儲(chǔ)在其他地方。異步過程完成后,draft 仍將被釋放

createDraft 和 finishDraft

createDraftfinishDraft 是兩個(gè)底層函數(shù),它們對(duì)于在 immer 之上構(gòu)建抽象的庫非常有用。避免了為了使用 draft 始終創(chuàng)建函數(shù)。

相反,人們可以創(chuàng)建一個(gè) draft,對(duì)其進(jìn)行修改,并在未來的某個(gè)時(shí)間完成該 draft,在這種情況下,將產(chǎn)生下一個(gè)不可變狀態(tài)。

例如,我們可以將上面的示例重寫為:

import { createDraft, finishDraft } from 'immer';
const user = { name: 'michel', todos: [] };
const draft = createDraft(user);
draft.todos = await (await fetch('http://host/' + draft.name)).json();
const loadedUser = finishDraft(draft);

五、性能提示

預(yù)凍結(jié)數(shù)據(jù)

當(dāng)向 Immer producer 中的狀態(tài)樹添加大型數(shù)據(jù)集時(shí)(例如從 JSON 端接收的數(shù)據(jù)),可以在首先添加的數(shù)據(jù)的最外層調(diào)用 freeze(json) 來淺凍結(jié)它。這將允許 Immer 更快地將新數(shù)據(jù)添加到樹中,因?yàn)樗鼘⒈苊膺f歸掃描和凍結(jié)新數(shù)據(jù)的需要。

可以隨時(shí)選擇退出

immer 在任何地方都是可選的,因此手動(dòng)編寫性能非??量痰?reducers ,并將 immer 用于所有普通的的 reducers 是非常好的。即使在 producer 內(nèi)部,您也可以通過使用 originalcurrent 函數(shù)來選擇退出 Immer 的某些部分邏輯,并對(duì)純 JavaScript 對(duì)象執(zhí)行一些操作。

對(duì)于性能消耗大的的搜索操作,從原始 state 讀取,而不是 draft

Immer 會(huì)將您在 draft 中讀取的任何內(nèi)容也遞歸地轉(zhuǎn)換為 draft。如果您對(duì)涉及大量讀取操作的 draft 進(jìn)行昂貴的無副作用操作,例如在非常大的數(shù)組中使用 find(Index) 查找索引,您可以通過首先進(jìn)行搜索,并且只在知道索引后調(diào)用 produce 來加快速度。這樣可以阻止 Immer 將在 draft 中搜索到的所有內(nèi)容都進(jìn)行轉(zhuǎn)換?;蛘撸褂?original(someDraft) 對(duì) draft 的原始值執(zhí)行搜索,這歸結(jié)為同樣的事情。

將 produce 拉到盡可能遠(yuǎn)的地方

始終嘗試將 produce “向上”拉動(dòng),例如 for (let x of y) produce(base, d => d.push(x))produce(base, d => { for (let x of y) ) d.push(x)}) 慢得多

六、陷阱

不要重新分配 recipe 參數(shù)

永遠(yuǎn)不要重新分配 draft 參數(shù)(例如:draft = myNewState)。相反,要么修改 draft,要么返回新狀態(tài)。

Immer 只支持單向樹

Immer 假設(shè)您的狀態(tài)是單向樹。也就是說,任何對(duì)象都不應(yīng)該在樹中出現(xiàn)兩次,也不應(yīng)該有循環(huán)引用。從根到樹的任何節(jié)點(diǎn)應(yīng)該只有一條路徑。

永遠(yuǎn)不要從 producer 那里顯式返回 undefined

可以從 producers 返回值,但不能以這種方式返回 undefined,因?yàn)樗c根本不更新 draft 沒有區(qū)別!

不要修改特殊對(duì)象

Immer 不支持特殊對(duì)象 比如 window.location

只有有效的索引和長度可以在數(shù)組上改變

對(duì)于數(shù)組,只能改變數(shù)值屬性和 length 屬性。自定義屬性不會(huì)保留在數(shù)組上。

只有來自 state 的數(shù)據(jù)會(huì)被 draft

請(qǐng)注意,來自閉包而不是來自基本 state 的數(shù)據(jù)將永遠(yuǎn)不會(huì)被 draft,即使數(shù)據(jù)已成為新 darft 的一部分

const onReceiveTodo = todo => {
  const nextTodos = produce(todos, draft => {
    draft.todos[todo.id] = todo;
    // 注意,因?yàn)?todo 來自外部,而不是 draft,所以他不會(huì)被 draft,
    // 所以下面的修改會(huì)影響原來的 todo!
    draft.todos[todo.id].done = true;
    // 上面的代碼相當(dāng)于
    todo.done = true;
    draft.todos[todo.id] = todo;
  });
};

始終使用嵌套 producers 的結(jié)果

支持嵌套調(diào)用 produce,但請(qǐng)注意 produce 將始終產(chǎn)生新狀態(tài),因此即使將 draft 傳遞給嵌套 produce,內(nèi)部 produce 所做的更改也不會(huì)在傳遞給它的 draft 中可見,只會(huì)反映在產(chǎn)生的輸出中。

換句話說,當(dāng)使用嵌套 produce 時(shí),您會(huì)得到 draft 的 draft,并且內(nèi)部 produce 的結(jié)果會(huì)被合并回原始 draft(或返回)

錯(cuò)誤示范:

// 嵌套的錯(cuò)誤寫法:
produce(state, draft => {
  produce(draft.user, userDraft => {
    userDraft.name += '!';
  });
});

正確示范:

// 嵌套的正確寫法:
produce(state, draft => {
  draft.user = produce(draft.user, userDraft => {
    userDraft.name += '!';
  });
});

Drafts 在引用上不相等

Immer 中的 draft 對(duì)象包裝在 Proxy 中,因此您不能使用 == 或 === 來測(cè)試原始對(duì)象與其 draft 之間的相等性,相反,可以使用 original:

const remove = produce((list, element) => {
  const index = list.indexOf(element); // 不會(huì)工作!
  const index = original(list).indexOf(element); // 用這個(gè)!
  if (index !== -1) {
    list.splice(index, 1);
  }
});
const values = [a, b, c];
remove(values, a);

如果可以的話,建議在 produce 函數(shù)之外執(zhí)行比較,或者使用 .id 之類的唯一標(biāo)識(shí)符屬性,以避免需要使用 original

以上就是Immer 功能最佳實(shí)踐示例教程的詳細(xì)內(nèi)容,更多關(guān)于Immer 功能教程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論