Zustand 是什麼? React 前端狀態管理

zustand

目錄

Zustand

 

Zustand 吉祥物

前言

為什麼會想要分享這個技術?

Zustatnd 本身是一個相較於 Redux,較為新穎的技術,也是我在工作時,團隊嘗試導入的前端狀態管理技術。透過實際的導入,有一些心得以及想法,才產生了寫這篇文章的念頭

現有架構遇到了什麼問題?

無論是使用 Redux 或是 Context 來去處理狀態管理,相對於 Zustand 都比較複雜,對於之後再加入此專案的工程師來說,勢必又是需要花時間去理解的。 Zustand 最主要的目標就是讓前端狀態管理,相對簡潔易懂,這樣的特性,也讓 Zustand 成為一些公司的選擇


關於狀態管理

在介紹 Zustand 之前,要來講講「狀態管理」是什麼?

現行網頁越來越複雜,也有了前端與後端的分別,其中前端比較偏向畫面呈現、UI \ UX、使用者流程、不同裝置有無跑版…等等
後端比較偏功能上的實作,包含資料庫管理、登入、註冊…等等

那麼針對前端越來越複雜,也開始有了框架的概念,而現行流行的三大框架,分別為 React、Vue、Angular。

不同框架也會有不同的「狀態管理」方式,例如:
React :基本上只需要管理資料,並且由「狀態」來去渲染
Vue:有資料與元件的「雙向綁定」

而基於 React 這個框架而言,又可以大致上可以分為

Local State

    • useState

    • useReducer

Context

    • useContext

Third Party

    • Redux

    • Mobx

    • Zustand

看到這邊應該可以理解,光是一個 React 框架中的「狀態管理」就已經相當複雜了

因此這篇只會針對 Zustand 來進行講解,也會用實例來進行介紹

我們也可以透過以下的套件下載總量對比來知道,過去一年中 Redux、Mobx、Zustand 盛行程度

Zustand npm doanload

 

https://npmtrends.com/mobx-vs-react-redux-vs-zustand


Zustand 是什麼?

根據 Zustand 官方說法,這是一個輕量、快速,基於 Flux 以及 Hook 概念出現的「狀態管理」套件

p.s. 上面那隻熊是Zustand標誌性的吉祥物!

其中 Zustand 也是為了解決一些複雜的React狀態管理問題,例如:Zombie Child Problem、React Concurrency、Context Loss

如果想要更了解這些問題是什麼的話,可以參考以下連結

Zombie Child Problem:React Redux 文件

React Concurrency:React 官方文件

React Context Loss:Stack Overflow

我們也可以透過 Zustand 官方知道他們想要擊敗 Redux 的野心


Zustand 用法

下面兩個簡單的用法,是針對 JavaScript 的使用者

建立第一個 Store

Zustand 是基於 Hook 建立的,因此第一個建立的就是由 Custom Hook 建立的 Store 裡面可以放入包含變數、物件、函數,並且可以透過 Set、Get 來去做資料的「狀態管理」

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

				
			
針對元件進行綁定

因為是使用 Hook,因此在任何元件中皆可以直接載入做使用
也可以針對這些狀態進行渲染,可以看到短短的幾行就可以處理完成,算是 Zustand 中的一大福音

這邊也針對 TypeScript 使用者提供簡單的 Zustand 範例

				
					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 TypeScript 寫法

在 Zustand 我們會使用 create 來去建立新的 store 
透過函數傳入,透過 Hook 來回傳狀態,並且進行再次渲染

				
					import create from "zustand"
type Product = { sku: string; name: string; image: string }
type CartState = {
  products: Product[]
  cart: { [sku: string]: number }
  addToCart: (sku: string) => void
  removeFromCart: (sku: string) => void
}
// Selectors
// ...
// Initialize our store with initial values and actions to mutate the state
export const useCart = create<CartState>(set => ({
products: [
// ...
],
cart: {},
// Actions
// ...
}))
				
			

這邊也另外展示 Redux 以及 Mobx 的寫法,讀者也可以比較其差異,不過這篇不會針對 Redux 或是 Mobx 進行講解,只會針對 Zustand 以及其狀態管理進行講解

Redux TypeScript 寫法

				
					import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit"
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
// Slices
// Definee the shape of the state and how to mutate it
type ICart = { [sku: string]: number }
const cartInitialState: ICart = {}
const cartSlice = createSlice({
  name: "cart",
  initialState: cartInitialState,
  reducers: {
  // ...
  },
})
type IProduct = { sku: string; name: string; image: string }
const productsInitialState: IProduct[] = [
  // ...
]
const productsSlice = createSlice({
  name: "products",
  initialState: productsInitialState,
  reducers: {},
})
// Actions
// ...
// Selectors
// ...
// Store
export const store = configureStore({
  reducer: {
  cart: cartSlice.reducer,
  products: productsSlice.reducer,
  },
})
// ...
const App = () => {
return (
  <Provider store={store}>
  <NavigationContainer>
  <Stack.Navigator>{/* ... */}</Stack.Navigator>
  <StatusBar style="auto" />
  </NavigationContainer>
  </Provider>
  )
}
				
			

Mobx TypeScript 寫法

				
					import { types, Instance } from "mobx-state-tree"
const Product = types.model({
  sku: types.string,
  name: types.string,
  image: types.string,
})
// Model and type our data with mobx state tree
const CartStore = types
  .model("CartStore", {
    products: types.array(Product),
    cart: types.map(types.number),
  })
  // Actions to mutate the state
  .actions(store => ({
    // ...
  }))
  // Views are like selectors
  .views(self => ({
    // ...
  }))
type CartStoreType = Instance<typeof CartStore>
// Spin up a hook to use our store and provide initial values to it
let _cartStore: CartStoreType
export const useCart = () => {
  if (!_cartStore) {
    _cartStore = CartStore.create({
      products: [
        // ...
      ],
      cart: {},
    })
  }
  return _cartStore
}
				
			

Zustand 是否能超越 Context 以及 Redux 呢?

 

https://github.com/pmndrs/zustand#why-zustand-over-redux

根據 Zustand 官方,列舉以上幾點,這邊也簡單講解一下

Redux

Redux 本身是一個較為複雜的狀態管理工具,會有 Actioins、View、State 的流程

以下是 Redux 的官方資料流示意圖,也可以知道如果要從無到有使用 Redux 的狀態管理工具學習門檻是比較高的

Redux Data Flow

 

Redux Data Flow

Redux Data Flow and React Component Life Cycle
Almost everyone who wants to learn Redux had seen this image before. It’s pretty straight forward for me right now, but…dev.to

Context

Context 本身雖然是歸類在狀態管理,但實際上比較像是一個集中式觸發狀態的方式

比起 Zustand 而言是會發散在各個檔案,不好進行管理

Context Data Flow

 

Context Data Flow

[React] Context API
To manage the data in React, you can use Props, State and Context. In this blog post, I will introduce what Context is…dev-yakuza.posstree.com


結語

使用這個技術的優勢

透過 Zustand,將複雜的商業邏輯中的,資料以及邏輯拆開,並且針對這些東西去做模組化。除了增加可讀性之外,也讓測試更容易去撰寫

使用心得

Zustand 本身是一個滿容易學習的狀態管理工具,且自己在工作產品使用上也是相對方便的,包含針對狀態進行 Set、Get 或是在元件中進行使用都是相對方便,滿推薦給想針對React進行狀態管理的讀者

感謝觀看~對於以上內容如有任何疑問或是想要討論的地方,都歡迎留言或是私訊我!

引用

https://github.com/pmndrs/zustand

https://react-redux.js.org/api/hooks#stale-props-and-zombie-children

https://docs.pmnd.rs/zustand/getting-started/comparison

其他文章參考

為何大公司都使用Nx ? Monorepo工具 5 分鐘快速建置

你真的懂Monorepo? 5 分鐘帶你認識前端大型架構

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

zh_TW繁體中文