Zustand 關於那隻熊你了解多少 也許不會是最好的但卻是最可愛的

me
林彥成
2024-10-02 | 3 min.
文章目錄
  1. 1. 為什麼不該再繼續用 Redux 了?
  2. 2. Zustand 專案分析
  3. 3. Zustand 設計模式: Slice Pattern
  4. 4. Zustand X 個人職涯發展
  5. 5. Zustand 對專案的優化
    1. 5.1. 支援 Redux Devtool
    2. 5.2. 支援狀態持久化到瀏覽器的儲存
    3. 5.3. 支援非同步
    4. 5.4. 支援 immer 進行多層的物件操作
    5. 5.5. 架構面: 提供在元件外操作共用的狀態
  6. 6. 2024 年為什麼推薦使用 Zustand

當我選擇 Zustand 作為狀態集中管理的解決方案時,有很多原因促使我做出這個決定。Zustand 的輕量級和彈性特性使其在架構上可以無縫穿梭於專案的各個角落,避免了因架構限制而導致的可讀性下降和優化難度增加。

以下是我使用 Zustand 快兩年後的一些心得,從幾個層面來分析。

為什麼不該再繼續用 Redux 了?

在選擇狀態集中管理的解決方案時,Zustand 的輕量級和彈性特性讓我不再依賴 Redux。

儘管 Redux 曾經為 FLUX 單向資料流提供接近典範的解決方案,但隨著時間推移,它逐漸暴露出以下問題:

  • 樣板程式碼繁多:Redux 需要大量的樣板程式碼,包括 actions、reducers 和 store 的設置,這增加了開發的複雜性。
  • 過度抽象:對於剛進入前端領域的開發者來說,Redux 的抽象層可能使簡單的狀態管理變得複雜,這對於小型或中型應用來說顯得多餘。而且,理解 Redux 的工作原理(如中間件和非同步處理)需要時間,可能影響團隊的開發效率。
  • 效能問題: 在狀態更新過程中,Redux 可能導致不必要的重新渲染,這在大型應用中會成為性能瓶頸。

隨著 React 的演進,如 Hooks 的加入,Redux 的生態系統顯得越來越過時,儘管 Redux Toolkit 嘗試解決這些問題,但歷史的包袱仍然存在。因此,我開始探索更好的選擇。

Zustand 專案分析

在使用 Zustand 快兩年後,我從以下幾個層面進行了分析:

  1. 簡單易用的文件:官方文檔清晰可讀,並具有可愛的設計和滾動視差效果
  2. 無需轉換 DevTool:從 Redux 轉移過來時,原有的 DevTool 可以直接使用,降低了學習成本
  3. 快速入門:QuickStart 實現迅速,讓開發者能夠迅速上手

官網簡潔易懂: https://zustand-demo.pmnd.rs/

import { create } from "zustand";

const useStore = create((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
}));

function Counter() {
  const { count, inc } = useStore();
  return (
    <div>
      <span>{count}</span>
      <button onClick={inc}>one up</button>
    </div>
  );
}

Zustand 使用 hooks 來管理狀態,這與 React 的設計理念更加契合,使得狀態的使用變得更直觀。

Zustand 設計模式: Slice Pattern

Zustand 提供的 Slice Pattern 使狀態管理更加模組化,便於組織和擴展。

const createFishSlice = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
});

const createBearSlice = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
});

import { create } from "zustand";
import { createBearSlice } from "./bearSlice";
import { createFishSlice } from "./fishSlice";

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}));

Zustand X 個人職涯發展

每次換新工作時,理論上都應該學會一次新的解決方案。

我記得最早使用 Zustand 時,因為專案時間緊迫,但我的老闆給了我很大的自由度,當時也是新專案而且時程有被報告追趕的壓力,於是投資了一隻看起來很可愛的熊當狀態管理的工具。

當然,我認為要選就要選可愛的?!

Zustand 對專案的優化

Zustand 的設計使其易於與其他函式庫(如 React Query)整合,並且提供 middleware,使需求的實現變得簡單。我建議維持 Single Source of Truth 的原則,以避免狀態不一致的問題。

Zustand 的進階寫法支援狀態持久化和多層的狀態更新,這些特性使開發變得更加方便。

接下來,熊熊稱霸世界?!

支援 Redux Devtool

import { devtools } from 'zustand/middleware'

// Usage with a plain action store, it will log actions as "setState"
const usePlainStore = create(devtools((set) => ...))
// Usage with a redux store, it will log full action types
const useReduxStore = create(devtools(redux(reducer, initialState)))

支援狀態持久化到瀏覽器的儲存

import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: "food-storage", // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
    }
  )
);

支援非同步

Zustand 支援非同步操作,開發者可以在 store 裡面實作 fetch 及 setState() 的邏輯。

const useFishStore = create((set) => ({
  fishies: {},
  fetch: async (pond) => {
    const response = await fetch(pond);
    set({ fishies: await response.json() });
  },
}));

支援 immer 進行多層的物件操作

import { create } from "zustand";
import { immer } from "zustand/middleware/immer";

const useBeeStore = create(
  immer((set) => ({
    bees: 0,
    addBees: (by) =>
      set((state) => {
        state.bees += by;
      }),
  }))
);

// --------------------

import { produce } from "immer";

const useLushStore = create((set) => ({
  lush: { forest: { contains: { a: "bear" } } },
  clearForest: () =>
    set(
      produce((state) => {
        state.lush.forest.contains = null;
      })
    ),
}));

const clearForest = useLushStore((state) => state.clearForest);
clearForest();

架構面: 提供在元件外操作共用的狀態

Zustand 允許開發者在元件外直接操作共用狀態,提高了靈活性。

底下是官網的範例可以透過已經持久化的 store 本身直接進行 getState() 或是 setState() 的狀態操作。

const useDogStore = create(() => ({ paw: true, snout: true, fur: true }));

// Getting non-reactive fresh state
const paw = useDogStore.getState().paw;
// Listening to all changes, fires synchronously on every change
const unsub1 = useDogStore.subscribe(console.log);
// Updating state, will trigger listeners
useDogStore.setState({ paw: false });

因為這樣的特性,Zustand 也提供了 vanillajs 的版本,便於在多種使用場景下使用。

import { createStore } from 'zustand/vanilla'

const store = createStore((set) => ...)
const { getState, setState, subscribe, getInitialState } = store

export default store

2024 年為什麼推薦使用 Zustand

在新的狀態管理方案中,Zustand 以其簡潔性和靈活性,讓我在開發中感到滿意,在專案需要協助和交接的過程,大多數時候不太需要多餘的解釋,接手的人不論資淺資深都可以快速的上手,使得團隊協作更加順利,隨著開發技術的演進,選擇合適的狀態管理工具變得至關重要,而 Zustand 無疑是當前最佳的選擇之一。


喜歡這篇文章,請幫忙拍拍手喔 🤣


share