[譯] Immer 下的不可突變數據和 React 的 setState

Immer 下的不可突變數據和 React 的 setState

Immer 是爲 JavaScript 不可突變性打造的一個很是棒的全新庫。以前像 Immutable.js 這樣的庫,它須要引入操做你數據的全部新方法。前端

它很不錯,可是須要複雜的適配器並在 JSON 和 不可突變 之間來回轉換,以便在須要時與其餘庫一塊兒使用。react

Immer 簡化了這一點,你能夠像往常同樣使用數據和 JavaScript 對象。這意味着當你須要考慮性能而且想知道數據什麼時候發生了變動,你可使用三個等號來作嚴格的全等檢查以及證實數據的確發生了變動。android

你對 shouldComponentUpdate 的調用再也不須要使用雙等或者全等去遍歷整個數據並進行比較。ios

文章截圖

注:此處爲截圖,原文爲視頻,建議看英文原文。git

對象展開運算符

在最新版本的 JavaScript 中,許多開發者依賴對象展開運算符來實現不可突變性。例如,你能夠展開以前的對象並覆蓋特定的屬性,或者增長新的屬性。它會在底層使用 Object.assign 並返回一個新對象。github

const prevObject = {
  id: "12345",
  name: "Jason",
};

const newObject = {
  ...prevObject,
  name: "Jason Brown",
};
複製代碼

咱們的 newObject 如今會是一個徹底不一樣的對象,因此任何全等判斷(prevObject === newObject)將會返回 false。因此它徹底建立了一個新對象。name 屬性也再也不是 Jason 而是會變成 Jason Brown,並且因爲咱們沒有對 id 屬性進行任何操做,因此它會保持不變。後端

這也適用於 React,由於 React 只會合併最外層的屬性,因此當你在 state 中有嵌套的對象時,你須要對以前的對象進行展開操做和更新。bash

讓咱們看一個例子。能夠看到咱們有兩個嵌套的計數器,可是咱們只想更新其中的一個而不影響另外一個。函數

import React, { Component } from "react";

class App extends Component {
  state = {
    count: {
      counter: 0,
      otherCounter: 5,
    },
  };

  render() {
    return <div className="App">{this.state.count.counter}</div>;
  }
}

export default App;
複製代碼

下一步在 componentDidMount 鉤子中,咱們將設置一個間隔定時器來更新咱們嵌套的計數器。不過,咱們但願保持 otherCounter 的值不變。因此,咱們須要使用對象展開運算符來把它從之前嵌套的 state 中帶過來。post

componentDidMount() {
    setInterval(() => {
      this.setState(state => {
        return {
          count: {
            ...state.count,
            counter: state.count.counter + 1,
          },
        };
      });
    }, 1000);
  }
複製代碼

這在 React 中是一個很是常見的場景。並且,若是你的數據是嵌套的很是深的,當你須要展開多個層級時,它會增長複雜性。

Immer Produce 基礎

Immer 仍然容許使用突變(直接改變值)而徹底無需擔憂如何去管理展開的層級,或者哪些數據咱們觸及過以及須要維持不可突變性。

讓咱們設置一個場景:你向計數器傳遞一個值來進行遞增,與此同時,咱們還有一個 user 對象是不須要被觸及的。

這裏咱們渲染咱們的應用並傳遞增量值。

ReactDOM.render(<App increaseCount={5} />, document.getElementById("root"));
複製代碼
import React, { Component } from "react";

class App extends Component {
  state = {
    count: {
      counter: 0,
    },
    user: {
      name: "Jason Brown",
    },
  };

  componentDidMount() {
    setInterval(() => {}, 1000);
  }

  render() {
    return <div className="App">{this.state.count.counter}</div>;
  }
}

export default App;
複製代碼

咱們像以前那樣設置了咱們的應用,如今咱們有一個 user 對象和一個嵌套的計數器。

咱們將導入 immer 並把它的默認值賦給 produce 變量。在給定當前 state 時,它將幫助咱們建立下一個 state。

import produce from "immer";
複製代碼

接下來,咱們將建立一個叫作 counter 的函數,它接收 state 和 props 做爲參數,這樣咱們就能夠讀取當前的計數,並基於 increaseCount 屬性更新咱們的下一次計數。

const counter = (state, props) => {};
複製代碼

Immer 的 produce 方法接收 state 做爲第一個參數,以及一個爲下一個狀態改變數據的函數做爲第二個參數。

produce(state, draft => {
  draft.count.counter += props.increaseCount;
});
複製代碼

若是你如今把他們放在一塊兒。咱們就能夠建立計數器函數,它接收 state 和 props 並調用 produce 函數。而後咱們按照對下一次狀態指望的樣子去改變 draft。Immer 的 produce 函數將爲咱們建立一個新的不可突變狀態。

const counter = (state, props) => {
  return produce(state, draft => {
    draft.count.counter += props.increaseCount;
  });
};
複製代碼

咱們更新後的間隔計數器函數大概會是這樣。

componentDidMount() {
    setInterval(() => {
      const nextState = counter(this.state, this.props);
      this.setState(nextState);
    }, 1000);
  }
複製代碼

不過咱們只是觸及過 countcounter,咱們的 user 對象上又發生了什麼呢?對象的引用是否也發生了變化?答案是否認的。Immer 確切的知道哪些數據是被觸及過的。因此,若是咱們在組件更新以後進行一次全等檢測,咱們能夠看到 state 中以前的 user 對象和以後的 user 對象是徹底相同的。

componentDidUpdate(prevProps, prevState) {
    console.log(this.state.user === prevState.user); // Logs true
  }
複製代碼

當你考慮性能而使用 shouldComponentUpdate 時,或者相似於 React Native 中FlatList 那樣,須要一種簡單的方式來知道某一行是否已經更新時,這就很是的重要。

Immer 柯里化

Immer 可使得操做更加簡單。若是它發現你傳遞的第一個參數是一個函數而不是一個對象,它就會爲你建立一個柯里化的函數。所以,produce 函數返回另外一個函數而不是一個新對象。

當它被調用時,它會把第一個參數用做你但願改變的 state,而後還會傳遞任何其餘參數。

所以,它不只僅是能夠建立一個計數器函數的(工廠)函數,就連 props 也會被代理。

const counter = produce((draft, props) => {
  draft.count.counter += props.increaseCount;
});
複製代碼

得益於 produce 返回一個函數,咱們能夠直接把它傳遞給 setState,由於 setState 有接收函數做爲參數的能力。當你正在引用以前的狀態時,你應該使用函數化的 setState(函數做爲第一個參數)。在咱們的場景中,咱們須要引用以前的計數來把它增長到新的計數。它將傳遞當前的 state 和 props 做爲參數,這也正是設置咱們的 counter 函數所須要的。

因此咱們的間隔計數器僅須要 this.setState 接收 counter 函數便可。

componentDidMount() {
    setInterval(() => {
      this.setState(counter);
    }, 1000);
  }
複製代碼

總結

這顯然是一我的爲的示例,但具備普遍的現實應用。能夠輕鬆比較僅更新了單個字段的一長串列表數據。大型嵌套表單只須要更新觸及過的特定部分。

你再也不須要作淺比對或者深比對,並且你如今能夠作全等檢查來準確的知道你的數據是否發生了變化,然後決定是否須要從新渲染。


Originally published at Code.

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索