React Context 的崩壞
狀態管理是 React 生態系中最受爭議的話題之一。多年以來,Redux 一直是毫無爭議的王者,但其龐大的樣板程式碼和陡峭的學習曲線讓許多團隊尋求替代方案。當 React 推出 Context API 時,許多開發者以為這是終極的狀態管理替代方案。不幸的是,在企業應用程式中,使用 Context 來處理快速變化的狀態會導致兩大嚴重的架構問題:Provider 地獄與不必要的重新渲染。
因為 Context 迫使你用多層 <Provider> 標籤包裹應用程式,你的元件樹會變得層層疊疊且難以閱讀。更糟糕的是,當 Context 物件中的單一值更新時,所有使用該 Context 的元件都會被迫重新渲染,即使它們只關心該 Context 中完全不相關的資料。
在 Smart Tech Devs,我們透過放棄笨重的 Redux 樣板程式碼以及避免 Context API 的陷阱,來架構高效能的 React 和 Next.js 應用程式。取而代之,我們使用 Zustand。Zustand 是一個極簡、非意見化的高速狀態管理函式庫,它在 React 元件樹之外運作,完全消除了 Provider 地獄。
Zustand 的理念
與 Context 不同,Zustand 採用原子化、基於訂閱的模型。你的狀態存活於 React 渲染週期之外的集中式 store 中。元件只使用「選擇器」來訂閱它們所需要的狀態切片。如果某個屬性更新,只有明確訂閱該特定屬性的元件才會重新渲染。
步驟 1:架構 Store(切片模式)
在大型應用程式中,將所有狀態放在單一大型檔案中是一種反模式。Zustand 允許我們使用「切片」模式來架構狀態,我們依據領域邏輯(例如 Auth、Cart、UI)分割 store,然後再合併它們。
// store/useStore.ts
import { create } from 'zustand';
// Define the shape of our User slice
interface AuthSlice {
user: { id: string; name: string } | null;
login: (userData: { id: string; name: string }) => void;
logout: () => void;
}
// Define the shape of our Cart slice
interface CartSlice {
items: Array<{ id: string; price: number }>;
addItem: (item: { id: string; price: number }) => void;
clearCart: () => void;
}
// Combine the types
type StoreState = AuthSlice & CartSlice;
// Create the unified store using the set function
export const useStore = create()((set) => ({
// Auth Slice Implementation
user: null,
login: (userData) => set(() => ({ user: userData })),
logout: () => set(() => ({ user: null })),
// Cart Slice Implementation
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
clearCart: () => set(() => ({ items: [] })),
}));
步驟 2:使用選擇器防止重新渲染
當我們使用狀態時,Zustand 的真正威力才展現出來。因為沒有 <Provider> 包裝器,我們可以在任何地方匯入 hook。不過,我們必須使用選擇器來確保嚴格的渲染邊界。
如果元件只需要 login 函式,它就不應該在新增購物車項目時重新渲染。
// components/LoginButton.tsx
import { useStore } from '@/store/useStore';
export default function LoginButton() {
// ✅ CORRECT: Selecting only the specific function.
// This component will NEVER re-render when cart items change.
const login = useStore((state) => state.login);
return (
login({ id: '1', name: 'John Doe' })}
className="bg-blue-600 text-white px-4 py-2 rounded"
>
Sign In
);
}
比較 React Context 的做法,存取 useContext(StoreContext) 會強制 LoginButton 在每次購物車總額更新時重新渲染。Zustand 原生解決了這個問題。
步驟 3:企業級中介軟體(持久化)
企業應用程式需要跨頁面重新載入的狀態持久化(例如保持使用者的購物車完整)。在 Redux 中,這需要使用 redux-persist 進行複雜的設定。在 Zustand 中,則可透過內建中介軟體立即實現。
// store/useStore.ts (Refactored with Persist Middleware)
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useStore = create()(
persist(
(set) => ({
user: null,
login: (userData) => set(() => ({ user: userData })),
// ... rest of state
}),
{
name: 'smart-tech-storage', // Key name in localStorage
partialize: (state) => ({ user: state.user }), // Only persist the user slice!
}
)
);
工程投資報酬率
透過採用 Zustand 和切片模式,你可以立即消除 React Provider 地獄,扁平化元件樹並大幅改善開發體驗。因為狀態邏輯與 React 的渲染生命週期解耦,你甚至可以從 React 元件之外(例如在標準工具函式或 Axios 攔截器中)存取和修改 store。此外,透過嚴格使用選擇器,你可以確保最佳的渲染效能,即使狀態物件成長到包含數千個屬性,你的應用程式仍能保持極高的速度。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.