欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Immer 功能最佳實踐示例教程

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

一、前言

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

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

閱讀這篇文章需要以下知識儲備:

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

三、歷史背景

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

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

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

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

這個時候,immer 誕生了!

四、immer 功能介紹

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

一個簡單的比較示例

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

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

不使用 Immer

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

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

使用 Immer

使用 Immer,這個過程更加簡單。我們可以利用  produce  函數(shù),它將我們要更改的 state 作為第一個參數(shù),對于第二個參數(shù),我們傳遞一個名為 recipe 的函數(shù),該函數(shù)傳遞一個  draft  參數(shù),我們可以對其應(yīng)用直接的  mutations。一旦  recipe  執(zhí)行完成,這些  mutations  被記錄并用于產(chǎn)生下一個狀態(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 就像擁有一個私人助理。助手拿一封信(當(dāng)前狀態(tài))并給您一份副本(草稿)以記錄更改。完成后,助手將接受您的草稿并為您生成真正不變的最終信件(下一個狀態(tài))。

第二個示例

如果有一個層級很深的對象,你在使用 redux 的時候,想在 reducer 中修改它的某個屬性,但是根據(jù) reduce 的原則,我們不能直接修改 state,而是必須返回一個新的 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ù)范式,同時使用普通的 JavaScript 對象、數(shù)組、Sets 和 Maps。無需學(xué)習(xí)新的 API 或 "mutations patterns"!
  • 強(qiáng)類型,無基于字符串的路徑選擇器等
  • 開箱即用的結(jié)構(gòu)共享
  • 開箱即用的對象凍結(jié)
  • 深度更新輕而易舉
  • 樣板代碼減少。更少的噪音,更簡潔的代碼

更新模式

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

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

更新對象

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' });
});
// 刪除最后一個元素
const updatedTodosArray = produce(todosArray, draft => {
  draft.pop();
});
// 刪除第一個元素
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 => {
  // 過濾器實際上會返回一個不可變的狀態(tài),但是如果過濾器不是處于對象的頂層,這個依然很有用
  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 對象。或者使用 async / await。這對于長時間運(yùn)行的進(jìn)程非常有用,只有在 Promise 鏈解析后才生成新對象

注意,如果 producer 是異步的,produce 本身也會返回一個 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();
});

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

createDraft 和 finishDraft

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

相反,人們可以創(chuàng)建一個 draft,對其進(jìn)行修改,并在未來的某個時間完成該 draft,在這種情況下,將產(chǎn)生下一個不可變狀態(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ù)集時(例如從 JSON 端接收的數(shù)據(jù)),可以在首先添加的數(shù)據(jù)的最外層調(diào)用 freeze(json) 來淺凍結(jié)它。這將允許 Immer 更快地將新數(shù)據(jù)添加到樹中,因為它將避免遞歸掃描和凍結(jié)新數(shù)據(jù)的需要。

可以隨時選擇退出

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

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

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

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

始終嘗試將 produce “向上”拉動,例如 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)是單向樹。也就是說,任何對象都不應(yīng)該在樹中出現(xiàn)兩次,也不應(yīng)該有循環(huán)引用。從根到樹的任何節(jié)點應(yīng)該只有一條路徑。

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

可以從 producers 返回值,但不能以這種方式返回 undefined,因為它與根本不更新 draft 沒有區(qū)別!

不要修改特殊對象

Immer 不支持特殊對象 比如 window.location

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

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

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

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

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

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

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

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

錯誤示范:

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

正確示范:

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

Drafts 在引用上不相等

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

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

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

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

相關(guān)文章

最新評論