Skip to main content

Zustand介紹

Zustand 是一個輕量級的狀態管理庫,專為 React 應用程式設計。它提供了簡單且直觀的 API,讓前端工程師可以輕鬆管理應用程式的全局狀態。與其他狀態管理庫(如 Redux)相比,Zustand 的特點是:

  1. 簡單易用:不需要繁瑣的配置或樣板程式碼,API 直觀且易於上手。

  2. 靈活:支援任何類型的狀態管理,無論是簡單的數值、物件還是非同步操作。

  3. 高效:僅重新渲染使用到特定狀態的元件,減少不必要的渲染。

  4. 無需 Provider:不像 Redux 或 React Context,Zustand 不需要將應用程式包裝在 Provider 元件中。

  5. 支援非同步操作:可以輕鬆處理 Promise 或 async/await。

  6. 輕量:程式碼庫小,安裝後對專案的影響極小。

Zustand 的核心概念是使用 create 函數創建一個 store,這個 store 包含狀態和更新狀態的函數。開發者可以透過 React Hook 的方式使用這些狀態,並在任何元件中存取或更新它們。

以下將詳細說明你提供的兩個範例,並以繁體中文、台灣用語和 JavaScript 程式碼完整呈現,確保步驟詳細且易於跟隨。


範例 1:主題切換 Store (useThemeStore)

import { create } from "zustand";

const useThemeStore = create((set) => ({
currentTheme: "sunset",
toggleTheme: () =>
set((state) => ({
currentTheme: state.currentTheme === "sunset" ? "winter" : "sunset",
})),
}));

export default useThemeStore;

說明

這個範例展示了一個簡單的主題切換 store,用於管理應用程式的主題(例如 "sunset" 或 "winter")。以下是程式碼的詳細解析和使用方式:

  1. 創建 Store

    • 使用 create 函數創建一個 Zustand store,傳入一個回呼函數,該函數接收 set 參數。

    • set 是一個用來更新狀態的函數,類似於 React 的 setState,但更靈活。

    • Store 回傳一個物件,包含狀態 (currentTheme) 和更新狀態的函數 (toggleTheme)。

  2. 狀態和函數

    • currentTheme:初始值為 "sunset",表示當前主題。

    • toggleTheme:一個函數,用於在 "sunset" 和 "winter" 之間切換主題。

      • 使用 set 函數更新 currentTheme,根據當前狀態 (state.currentTheme) 決定新值。

      • set 接受一個函數,該函數接收當前狀態 (state),並回傳要更新的狀態物件。

  3. 使用方式

    • 在 React 元件中,可以直接匯入並使用 useThemeStore 來存取狀態或呼叫函數。

    • 範例使用程式碼如下:

      import React from 'react';
      import useThemeStore from './useThemeStore';

      function ThemeToggle() {
      // 從 store 中解構出 currentTheme 和 toggleTheme
      const { currentTheme, toggleTheme } = useThemeStore();

      return (
      <div>
      <p>當前主題:{currentTheme}</p>
      <button onClick={toggleTheme}>切換主題</button>
      </div>
      );
      }

      export default ThemeToggle;
  4. 步驟解析

    • 匯入 store:在元件中匯入 useThemeStore。

    • 使用 Hook:透過 useThemeStore() 取得 currentTheme 和 toggleTheme。

    • 渲染狀態:在 JSX 中顯示 currentTheme。

    • 觸發更新:點擊按鈕時呼叫 toggleTheme,這會觸發 set 更新狀態,進而重新渲染元件。

    • Zustand 的高效性:只有使用 currentTheme 的元件會重新渲染,無關的元件不會受到影響。

  5. 優點

    • 程式碼簡潔,無需額外的樣板程式碼。

    • 狀態邏輯集中在 store 中,易於維護。

    • 支援簡單的狀態切換邏輯,適合管理主題、模式等簡單狀態。


範例 2:認證管理 Store (useAuthStore)

import { create } from "zustand";
import { openDB } from "idb";

const DB_NAME = "auth-db";
const STORE_NAME = "auth";
const USER_KEY = "user";

// 取得 user
async function getUserFromIDB() {
const db = await openDB(DB_NAME, 1, {
upgrade(db) {
db.createObjectStore(STORE_NAME);
},
});
return await db.get(STORE_NAME, USER_KEY);
}

// 設定 user
async function setUserToIDB(user) {
const db = await openDB(DB_NAME, 1);
await db.put(STORE_NAME, user, USER_KEY);
}

// 移除 user
async function removeUserFromIDB() {
const db = await openDB(DB_NAME, 1);
await db.delete(STORE_NAME, USER_KEY);
}

const useAuthStore = create((set) => ({
isLoggedIn: false,
user: null,
isLoading: true, // 新增
init: async () => {
set({ isLoading: true });
const user = await getUserFromIDB();
set({ isLoggedIn: !!user, user: user || null, isLoading: false });
},
login: async (user) => {
await setUserToIDB(user);
set({ isLoggedIn: true, user });
},
logout: async () => {
await removeUserFromIDB();
set({ isLoggedIn: false, user: null });
},
}));

export default useAuthStore;

說明

這個範例展示了一個更複雜的認證管理 store,結合了 IndexedDB(透過 idb 庫)來持久化儲存使用者資料。以下是詳細解析和使用方式:

  1. IndexedDB 輔助函數

    • getUserFromIDB:從 IndexedDB 中取得使用者資料。

      • 使用 openDB 開啟名為 "auth-db" 的資料庫,版本為 1。

      • 如果資料庫需要升級(upgrade),則創建一個名為 "auth" 的物件儲存區。

      • 使用 db.get 從 "auth" 儲存區中取得鍵為 "user" 的資料。

    • setUserToIDB:將使用者資料儲存到 IndexedDB。

      • 開啟資料庫並使用 db.put 將 user 物件儲存到鍵 "user"。
    • removeUserFromIDB:從 IndexedDB 中移除使用者資料。

      • 開啟資料庫並使用 db.delete 刪除鍵 "user" 的資料。
  2. 創建 Store

    • 使用 create 創建一個名為 useAuthStore 的 store。

    • Store 包含以下狀態和函數:

      • isLoggedIn:布林值,表示使用者是否已登入,初始為 false。

      • user:儲存使用者資料,初始為 null。

      • isLoading:布林值,表示是否正在載入資料,初始為 true。

      • init:非同步函數,用於初始化 store,從 IndexedDB 載入使用者資料。

      • login:非同步函數,處理登入邏輯,將使用者資料儲存到 IndexedDB 並更新狀態。

      • logout:非同步函數,處理登出邏輯,從 IndexedDB 移除資料並重置狀態。

  3. 狀態初始化

    • init 函數在應用程式啟動時呼叫,用於從 IndexedDB 載入使用者資料。

    • 首先設置 isLoading: true,表示正在載入。

    • 從 IndexedDB 取得使用者資料,根據是否取得資料設置 isLoggedIn 和 user。

    • 最後設置 isLoading: false,表示載入完成。

  4. 使用方式

    • 在 React 元件中,可以使用 useAuthStore 來存取認證狀態並執行登入/登出操作。

    • 範例使用程式碼如下:

      import React, { useEffect } from 'react';
      import useAuthStore from './useAuthStore';

      function AuthComponent() {
      // 從 store 中解構出狀態和函數
      const { isLoggedIn, user, isLoading, init, login, logout } = useAuthStore();

      // 在元件掛載時初始化 store
      //透過 useEffect,我們確保 init 函數在元件掛載時(即元件首次出現在畫面上時)自動執行一次,而不是在每次渲染時都執行
      useEffect(() => {
      init();
      }, [init]);

      // 模擬登入
      const handleLogin = () => {
      const user = { id: 1, name: '使用者名稱' };
      login(user);
      };

      // 模擬登出
      const handleLogout = () => {
      logout();
      };

      if (isLoading) {
      return <div>載入中...</div>;
      }

      return (
      <div>
      {isLoggedIn ? (
      <div>
      <p>歡迎,{user?.name}</p>
      <button onClick={handleLogout}>登出</button>
      </div>
      ) : (
      <div>
      <p>尚未登入</p>
      <button onClick={handleLogin}>登入</button>
      </div>
      )}
      </div>
      );
      }

      export default AuthComponent;
  5. 步驟解析

    • 初始化:在 useEffect 中呼叫 init 函數,確保元件掛載時從 IndexedDB 載入資料。

    • 顯示載入狀態:當 isLoading 為 true 時,顯示「載入中」。

    • 根據登入狀態渲染:根據 isLoggedIn 決定顯示登入或登出介面。

    • 執行登入/登出:點擊按鈕觸發 login 或 logout,這些函數會更新 IndexedDB 和 store 狀態,進而觸發元件重新渲染。

    • 持久化儲存:使用 IndexedDB 確保使用者資料在頁面重新整理後依然保留。

  6. 優點

    • 結合 IndexedDB 實現持久化狀態管理,適合需要儲存使用者資料的場景。

    • 非同步操作(如資料庫存取)與 Zustand 無縫整合。

    • 狀態邏輯清晰,易於維護和擴展。

    • isLoading 狀態方便處理非同步操作的 UI 顯示。


總結

  • useThemeStore:一個簡單的主題切換 store,展示 Zustand 的基本用法,適合管理簡單的狀態切換。

  • useAuthStore:一個複雜的認證管理 store,結合 IndexedDB 實現持久化儲存,展示 Zustand 處理非同步操作的能力。

  • Zustand 的優勢:簡單、靈活、高效,無需繁瑣配置,適合前端工程師快速構建狀態管理邏輯。

  • 操作步驟

    1. 安裝 Zustand:npm install zustand。

    2. 創建 store 並定義狀態和函數。

    3. 在元件中匯入並使用 store,透過 Hook 存取狀態或呼叫函數。

    4. 根據需求結合非同步操作(如 IndexedDB)或簡單的狀態更新。