天天看點

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

大家好,很高興又見面了,我是"前端‬進階‬",由我帶着大家一起關注前端前沿、深入前端底層技術,大家一起進步,也歡迎大家關注、點贊、收藏、轉發!

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

前端‬進階

今天給大家帶來是不同的狀态管理庫,redux vs jotai vs mobx vs recoil vs zustand。話不多說,直接開始!

1.什麼是Redux?

Redux 是 JavaScript 應用程式的可預測狀态容器。可以幫助開發者編寫行為一緻、在不同環境(用戶端、伺服器和本機)中運作、且易于測試的應用程式。 最重要的是,它提供了出色的開發人員體驗,例如實時代碼編輯與時間旅行調試器等能力。

您可以将 Redux 與 React 或任何其他視圖庫一起使用。而且Redux很小,包括所有依賴項,隻有1.8kb左右大小。

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

Redux體積資訊

請參閱下圖以了解 Redux 的工作原理:

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

圖檔來自Ishrat Umar《What is React State Management?》

Redux 的優點是它的社群支援、伺服器端渲染、易于調試和狀态持久化。您可以将 Redux 與 React 或任何其他庫一起使用。下面是redux的簡單示例:

import { createStore } from 'redux'
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}
let store = createStore(counter)
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
           

2.什麼是mbox?

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

什麼是mbox?

2.1 mbox特點

MobX 是一個久經考驗的狀态管理庫,它通過透明地應用函數式、反應式程式設計使狀态管理變得簡單和可擴充。 MobX 背後的哲學很簡單:

直截了當

編寫簡約、無樣闆的代碼來捕捉開發者意圖。比如,更改字段值隻需使用普通的 JavaScript 指派操作即可。反應系統将自動檢測所有更改并将更改傳遞到使用的地方。 在異步過程中更新資料時也不需要特殊工具。

最佳渲染

在運作時跟蹤資料的所有更改和使用,建構一個捕獲狀态和輸出之間所有關系的依賴樹。 這保證了依賴狀态的計算,比如 React 元件,隻在嚴格需要時運作。 無需使用記憶和選擇器等容易出錯和次優的技術手動優化元件。

建構自由

MobX 是獨立的,允許你在任何 UI 架構之外管理應用程式狀态。 這使代碼解耦、可移植,最重要的是,易于測試。

2.2 mbox示例

import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"
// Model the application state.
class Timer {
    secondsPassed = 0
    constructor() {
        makeAutoObservable(this)
    }
    increase() {
        this.secondsPassed += 1
    }
    reset() {
        this.secondsPassed = 0
    }
}
const myTimer = new Timer()
// Build a "user interface" that uses the observable state.
const TimerView = observer(({ timer }) => (
    <button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
// Update the 'Seconds passed: X' text every second.
setInterval(() => {
    myTimer.increase()
}, 1000)           

上面的示例中,TimerView React 元件周圍的觀察者包裝器将自動檢測到渲染依賴于 timer.secondsPassed這個可觀察對象,即使這種關系沒有明确定義。 當該字段在未來更新時,反應系統将負責重新渲染元件。

每個事件 (onClick / setInterval) 都會調用更新可觀察狀态 (myTimer.secondsPassed) 的操作 (myTimer.increase / myTimer.reset)。 可觀察狀态的變化被精确地傳播到所有依賴于所做變化的計算和副作用(TimerView)。

3.什麼是recoil?

3.1 為什麼需要recoil

出于相容性、簡單性的諸多原因,最好使用 React 的内置狀态管理功能。 但是 React 有一定的局限性:

  • 元件狀态隻能通過将其傳遞到共同的祖先來共享,但元件樹可能很大。
  • 上下文隻能存儲單個值,而不是一組不确定的值,每個值都有自己的消費者。
  • 這兩者都使得很難從樹的葉子(使用狀态的地方)代碼拆分樹的頂部(狀态必須存在的地方)。
Recoil希望改進這一點,同時盡可能保持 API 以及語義和行為和React一緻。

Recoil 定義了一個有向圖,它正交于 React 樹,但也是固有的并附加到你的 React 樹。 狀态變化從這個圖的根(我們稱之為原子)通過純函數(我們稱之為選擇器)流入元件。 使用這種方法:

  • 得到了一個無樣闆的 API,其共享狀态具有與 React 本地狀态相同的get/set接口
  • 有可能與并發模式和其他可用的新 React 功能相容
  • 狀态定義是增量的和分布式的,使代碼分割成為可能
  • 狀态可以用派生資料替換,而無需修改使用它的元件
  • 派生資料可以在同步和異步之間選擇,而無需修改使用元件
  • 可以将導航視為一流的概念,甚至可以在連結中編碼狀态轉換
  • 以向後相容的方式持久儲存整個應用程式狀态很容易,是以持久化的狀态可以在應用程式更改後繼續存在

3.2 核心概念

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

recoil的原子和選擇器關系。圖檔來自Ishrat Umar《What is React State Management?》

原子

原子是狀态, 它們是可更新和可訂閱的:當一個原子更新時,每個訂閱的元件都會用新值重新渲染,它們也可以在運作時建立。 原子可以用來代替 React 本地元件狀态。如果多個元件使用同一個原子,則所有這些元件共享它們的狀态。

原子是使用 atom 函數建立的:

const fontSizeState = atom({
  key: 'fontSizeState',
  default: 14,
});           

原子需要一個唯一的key,用于調試、持久化和某些進階 API,以用于檢視所有原子的映射。

兩個原子擁有相同的key是錯誤的,是以請確定它們是全局唯一的。 與 React 元件狀态一樣,它們也有一個預設值。

要從元件讀取和寫入原子,可以使用一個名為 useRecoilState 的鈎子。 它就像 React 的 useState,但現在狀态可以在元件之間共享:

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return (
    <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}           

單擊該按鈕會将按鈕的字型大小增加,但是其他一些元件也可以使用相同的字型大小:

function Text() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return <p style={{fontSize}}>This text will increase in size too.</p>;
}           

選擇器

選擇器是一個純函數,它接受原子或其他選擇器作為輸入。 當這些上遊原子或選擇器更新時,選擇器功能将被重新執行。 元件可以像原子一樣訂閱選擇器,然後在選擇器更改時重新渲染。

選擇器用于計算基于狀态的派生資料,進而避免備援狀态,因為最小狀态集存儲在原子中,而其他一切都作為該最小狀态的函數有效計算。 由于選擇器跟蹤哪些元件需要它們以及它們所依賴的狀态,是以這種函數式方法非常有效。

從元件的角度來看,選擇器和原子具有相同的接口,是以可以互相替代。選擇器使用 selector 函數定義:

const fontSizeLabelState = selector({
  key: 'fontSizeLabelState',
  get: ({get}) => {
    const fontSize = get(fontSizeState);
    const unit = 'px';

    return `${fontSize}${unit}`;
  },
});           

get 屬性是要計算的函數, 它可以使用傳遞給它的 get 參數通路原子和其他選擇器的值。 每當它通路另一個原子或選擇器時,就會建立一個依賴關系,這樣更新另一個原子或選擇器将導緻重新計算這個原子或選擇器。

在此 fontSizeLabelState 示例中,選擇器具有一個依賴項:fontSizeState 原子。 從概念上講,fontSizeLabelState 選擇器的行為類似于一個純函數,它将 fontSizeState 作為輸入并傳回格式化的字型大小标簽作為輸出。

可以使用 useRecoilValue() 讀取選擇器,它将原子或選擇器作為參數并傳回相應的值。 我們不使用 useRecoilState(),因為 fontSizeLabelState 選擇器不可寫:

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);
  return (
    <>
      <div>Current font size: {fontSizeLabel}</div>

      <button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    </>
  );
}           

4.jotai

4.1 什麼是Jotai?

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

什麼是Jotai?

Jotai 從簡單的 useState 替代品擴充到企業 TypeScript 應用程式。其具有以下核心特點:

  • 最小核心 API (2kb)
  • 諸多實用程式和內建
  • 無字元串keys(與 Recoil 相比)

4.2 jotai簡單用例

建立一個原始原子

一個原子代表一個狀态。您隻需要指定一個初始值,它可以是原始值,如字元串和數字、對象和數組。可以根據需要建立任意數量的原始原子。

import { atom } from 'jotai'
const countAtom = atom(0)
const countryAtom = atom('Japan')
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka'])
const mangaAtom = atom({ 'Dragon Ball': 1984, 'One Piece': 1997, Naruto: 1999 })           

原子可以像 React.useState 一樣使用:

import { useAtom } from 'jotai'

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  return (
    <h1>
      {count}
      <button onClick={() => setCount((c) => c + 1)}>one up</button>
      ...           

使用計算值建立派生原子

通過将讀取函數作為第一個參數傳遞,可以從現有原子建立一個新的隻讀原子。 get 允許您擷取任何原子的上下文值。

const doubledCountAtom = atom((get) => get(countAtom) * 2)
function DoubleCounter() {
  const [doubledCount] = useAtom(doubledCountAtom)
  return <h2>{doubledCount}</h2>
}           

從多個原子建立一個原子

您可以組合多個原子來建立派生原子。

const count1 = atom(1)
const count2 = atom(2)
const count3 = atom(3)
const sum = atom((get) => get(count1) + get(count2) + get(count3))           

或者通過如下方式:

const atoms = [count1, count2, count3, ...otherAtoms]
const sum = atom((get) => atoms.map(get).reduce((acc, count) => acc + count))           

建立一個可寫的派生原子

在第二個參數中指定一個寫函數。 get 将傳回原子的目前值,set 将更新原子值。

const decrementCountAtom = atom(
  (get) => get(countAtom),
  (get, set, _arg) => set(countAtom, get(countAtom) - 1)
)

function Counter() {
  const [count, decrement] = useAtom(decrementCountAtom)
  return (
    <h1>
      {count}
      <button onClick={decrement}>Decrease</button>
      ...           

異步操作

準備好後,隻需将寫入函數設為異步函數并調用 set 即可。

const fetchCountAtom = atom(
  (get) => get(countAtom),
  async (_get, set, url) => {
    const response = await fetch(url)
    set(countAtom, (await response.json()).count)
  }
)

function Controls() {
  const [count, compute] = useAtom(fetchCountAtom)
  return (
    <button onClick={() => compute('http://count.host.com')}>compute</button>
    ...           

5.zustand

5.1 什麼是zustand

Zustand 這樣的庫更務實(Pragmatically)和直接地管理全局狀态。Zustand有以下特征:

  • 可擴充且速度快
  • 它不需要樣闆
  • 與 Redux 相比,它沒有那麼複雜
  • 更少的代碼行
  • 開發商積極響應

5.2 使用zustand

首先建立一個store,store是一個鈎子!您可以在其中放入任何東西:基元、對象、函數。狀态必須不可變地(immutable)更新,set 函數合并狀态來幫助它。

import { create } from 'zustand'

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))           

然後綁定你的元件:

function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here ...</h1>
}

function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}           

更多zustand用法示例可以參考文末資料。

6.jotai vs mobx vs recoil vs redux vs zustand?

下圖所示,從star來說:redux(59,230) > zustand(27,346) > mbox(26,141) > recoil(18,366) > jotai(12,119)。而從檔案大小來說:zustand(1.1k)< redux(1.8k) < jotai(2.3k)< mobx(16.5k)< recoil(23.4k)

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

再看看NPM的下載下傳量,如下圖:

redux vs jotai vs mobx vs recoil vs zustand!誰是最好的狀态管理庫?

在過去一年,redux一騎紅塵,将其他狀态庫遠遠甩在後面。mbox緊随其後,zustand次之,jotai最小。

7.本文總結

本文主要和大家介紹不同的狀态管理庫,redux vs jotai vs mobx vs recoil vs zustand。對每一個庫的原理、用法做了一個簡單的介紹,同時從Github資料、NPM下載下傳量對各個庫做了對比,因為篇幅有限,沒有繼續展開,但是文末的參考資料提供了大量優秀文檔以供學習,如果有興趣可以自行閱讀。如果大家有什麼疑問歡迎在評論區留言。

參考資料

https://www.npmjs.com/package/redux

https://www.npmjs.com/package/mobx

https://recoiljs.org/docs/introduction/core-concepts

https://www.npmjs.com/package/jotai

https://www.npmjs.com/package/zustand

https://javascript.plainenglish.io/what-is-react-state-management-1937c49de5c4

https://dmitripavlutin.com/react-jotai-state-management/

繼續閱讀