2020要用immer來代替immutable優化你的React項目

不可變數據

React的老手們早就知道爲何要用不可變數據了,可是爲了防止新手們看不懂,因此仍是要解釋一下什麼是不可變數據,不可變數據指的其實就是當你修改一個數據的時候,這個數據會給你返回一個新的引用,而本身的引用保持不變,有點像是常常用到的數組的map方法:javascript

const arr1 = [1, 2, 3];
const arr2 = arr1.map(item => item * 10);

console.log(arr1 === arr2)
//false
複製代碼

這樣的話每次修改數據,新返回的數據就和原來不相等了。java

若是數據變動,節點類型不相同的時候會怎樣呢?React 的作法很是簡單粗暴,直接將 原 VDOM 樹上該節點以及該節點下全部的後代節點 所有刪除,而後替換爲新 VDOM 樹上同一位置的節點,固然這個節點的後代節點也全都跟着過來了。react

這樣的話很是浪費性能,父組件數據一變化,子組件所有都移除,再換新的,因此纔有了shouldComponentUpdate這個生命週期(Vue的小夥伴請放心,Vue原理和React不太同樣,因此沒這毛病),這個函數若是返回false的話子組件就不會更新,可是每次在這個函數裏面寫對比會很麻煩,因此有了PureComponent和Memo,可是隻提供了淺比較,因此這時候不可變數據就派上用場了,每次修改數據都和原數據不相等的話,就能夠精確的控制更新。git

immutable

Facebook早就知道React這一缺陷,因此歷時三年打造了一個不可變數據的immutable.js。它內部實現了一套完整的 Persistent Data Structure,還有不少易用的數據類型。像Collection、List、Map、Set、Record、Seq。有很是全面的map、filter、groupBy、reduce``find函數式操做方法。同時 API 也設計的和JS對象、數組等相似。
不過功能雖全,可是若是咱們僅僅只是爲了優化淺對比防止子組件過分刷新的話,引入這麼大的一個庫就未免有些大材小用了,並且學習成本也是須要考慮在內的,因此要爲你們介紹一下今天的主角:輕量、易用、簡潔又能夠快速上手的immer.jsgithub

immer

immer這玩意來頭可不小,他的創造者就是大名鼎鼎的Mobx做者,聽過Mobx的人應該都知道,它與Redux相比更簡潔、更輕量、同時也更加易學,因此immer也一樣的繼承了這些優勢:輕量、簡潔、易上手、而且使用起來也很是的舒服,不會產生容易把immutable數據類型與原生JS數據類型搞混的狀況。它的核心思想就是利用Vue3源碼中大量運用的Proxy代理,幾乎以最小的成本實現了JS的不可變數據結構,解決了許多平常開發中的棘手問題,相信看完個人文章你必定會喜歡上它的!
首先第一步就是先進行安裝:shell

npm i -S immer
複製代碼

或者npm

yarn add immer
複製代碼
import produce from 'immer';

const array = [{value: 0}, {value: 1}, {value: 2}];
const arr = produce(array, draft => {
  draft[0].value = 10;
});

console.log(arr === array);
//false
複製代碼

解釋一下:produce是生產的意思(你想起啥名都行,可是官網喜歡這麼叫,我就跟着這麼起名),這個函數第一個參數是你想要改變的數據對象,第二個參數是一個函數,這個函數的參數draft是草稿的意思,表明的就是你想要改變的那個數據對象,而後在函數體內你就正常想怎麼改就怎麼改,produce運行完的結果就是一個全新的對象啦!怎麼樣是否是超級簡潔超級好用呢?數組

  • 注意:若是你什麼也不返回或者並無操做數據的話,並不會返回一個新的對象!
const array = [{value: 0}, {value: 1}, {value: 2}];
const arr = produce(array, draft => {});

console.log(array === arr);
// true
複製代碼

引用一張immutable的圖,從圖中能夠看出來返回值並非一份深拷貝內容,而是共享了未被修改的數據,這樣的好處就是避免了深拷貝帶來的極大的性能開銷問題,而且更新後返回了一個全新的引用,即便是淺比對也能感知到數據的改變。

  • 若是把produce的第一個參數省略掉的話,只傳入第二個參數返回值將會是一個函數👇
const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = produce((draft) => {
  draft[0].value = 10;
});
const arr = producer(array);

console.log(array === arr);
// false
複製代碼

這樣雖然結果同樣,可是卻加強了可複用性,甚至能夠進行再次封裝來造成一個高階函數:數據結構

const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = (state, fn) => produce(fn)(state);
const arr = producer(array, draft => { draft[0] = 666 });

console.log(array, arr);
// [{…}, {…}, {…}]
// [666, {…}, {…}]
複製代碼
  • 此時咱們並無任何返回值,那麼若是有返回值的話會怎樣呢?
const array = [{value: 0}, {value: 1}, {value: 2}];
const producer = (state, fn) => produce(fn)(state);
const arr = producer(array, draft => [666, ...draft]);

console.log(array, arr);
// [{…}, {…}, {…}]
// [666, {…}, {…}, {…}]
複製代碼

咱們發現返回值就是新數據的結果!因此咱們能夠清楚的得知:在沒有返回值時數據是根據函數體內對draft參數的操做生成的。有返回值的話返回值就會被當作新數據來返回。函數

使用use-immer來替代你的useState

因爲React Hooks的異軍突起,致使如今不少組件都使用函數來進行編寫,數據就直接寫在useState中,可是有了useImmer,你之後就能夠用它來代替useState啦!
仍是老規矩,先安裝:

npm install immer use-immer
複製代碼

yarn add immer use-immer
複製代碼

用法

定義數據: const [xxx, setXxx] = useImmer(…)
修改數據: setXxx(draft => {})

能夠看到用法和setState幾乎沒啥太大區別,接下來咱們經過一個小案例來繼續深刻useImmer的用法:

import React from "react";
import { useImmer } from "use-immer";


export default function () {
  const [person, setPerson] = useImmer({
    name: "馬雲",
    salary: '對錢沒興趣'
  });

  function setName(name) {
    setPerson(draft => {
      draft.name = name;
    });
  }

  function becomeRicher() {
    setPerson(draft => {
      draft.salary += '$¥';
    });
  }

  return (
    <div className="App"> <h1> {person.name} ({person.salary}) </h1> <input onChange={e => { setName(e.target.value); }} value={person.name} /> <br /> <button onClick={becomeRicher}>變富</button> </div> ); } 複製代碼

這是一個改編自官網的小例子,能夠看得出useImmer的用法和useState十分類似,在保持住了簡潔性的同時還具有了immutable的數據結構,十分便捷。

useImmerReducer

use-immer對useReducer進行了增強封裝,一樣也幾乎沒什麼學習成本,再改編一下官網小案例👇

import React from "react";
import { useImmerReducer } from "use-immer";

const initialState = { salary: 0 };

function reducer(draft, action) {
  switch (action.type) {
    case "reset":
      return initialState;
    case "increment":
      return void draft.salary++;
    case "decrement":
      return void draft.salary--;
  }
}

export default function () {
  const [state, dispatch] = useImmerReducer(reducer, initialState);
  return (
    <> 期待工資: {state.salary}K <button onClick={() => dispatch({ type: "increment" })}>+</button> <button onClick={() => dispatch({ type: "decrement" })}>-</button> <button onClick={() => dispatch({ type: "reset" })}>重置</button> </> ); } 複製代碼

怎麼樣?看完以後是否是感受神清氣爽,有這麼一個東西輕量、簡潔、易用又好學,看一篇文章的功夫就能學會,並且還能很好的解決你的React性能問題,那還等什麼?趕忙npm install下載安裝吧!
相關文章
相關標籤/搜索