又一知名前端庫(kù)停止維護(hù)...
最近,GitHub 上擁有 20k Star 的 React 狀態(tài)管理庫(kù) Recoil 正式停止維護(hù),其 GitHub 倉(cāng)庫(kù)已被歸檔。Recoil 由 Meta 公司開源,然而,值得注意的是,Meta 已解雇 Recoil 團(tuán)隊(duì)的所有成員,且該庫(kù)已有接近兩年的時(shí)間未進(jìn)行更新,因此其停止維護(hù)似乎已成定局。
圖片
在 2025 年的當(dāng)下,提到 React 狀態(tài)管理,我依舊首推 Zustand,它比 Redux、Mobx 的思維模型更簡(jiǎn)單,沒有那么復(fù)雜的樣板代碼要寫。下面就來簡(jiǎn)單看看 Zustand 的用法。
Zustand 是什么?
Zustand 是一個(gè)小型、快速且可擴(kuò)展的狀態(tài)管理解決方案,基于簡(jiǎn)化的 Flux 原則,使用基于 Hooks 的 API,不包含樣板代碼且不具有強(qiáng)制性。Zustand 在 Github 擁有近 50k Star,其 npm 每周下載量近 500w。
Zustand 的特點(diǎn)如下:
- 簡(jiǎn)單性:與 Redux 相比,Zustand 更簡(jiǎn)單且不具有強(qiáng)制性,不需要像 React-Redux 那樣使用 Context Provider 包裹應(yīng)用。
- 基于 Hooks:提供了直觀易用的 Hooks 接口,讓開發(fā)者可以輕松地與狀態(tài)進(jìn)行交互,減少樣板代碼。
- 單一數(shù)據(jù)源:整個(gè)應(yīng)用的狀態(tài)被集中存儲(chǔ)在一個(gè) store 中,該 store 可以被分割成多個(gè)狀態(tài)切片,每個(gè)切片負(fù)責(zé)一部分應(yīng)用邏輯。
- 不可變性:狀態(tài)更新是不可變的,更新狀態(tài)時(shí)需要?jiǎng)?chuàng)建一個(gè)新的狀態(tài)對(duì)象,而不是直接修改現(xiàn)有狀態(tài),從而簡(jiǎn)化狀態(tài)管理并防止常見的可變性相關(guān)錯(cuò)誤。
- 訂閱和選擇性響應(yīng)性:組件可以訂閱特定的狀態(tài)切片,并在這些切片發(fā)生變化時(shí)自動(dòng)重新渲染。
- 細(xì)粒度依賴跟蹤:使用代理實(shí)現(xiàn)對(duì)狀態(tài)變化的細(xì)粒度跟蹤,確保只有當(dāng)相關(guān)狀態(tài)發(fā)生變更時(shí)才會(huì)觸發(fā)組件的重新渲染,最大限度減少了不必要的更新。
Zustand 的使用
基本使用
安裝 Zustand:首先,使用 npm 來安裝 Zustand:
npm install zustand
創(chuàng)建 Store:在 Zustand 中,Store是通過create函數(shù)創(chuàng)建的。每個(gè)Store都包含狀態(tài)和處理狀態(tài)的函數(shù)。
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0, // 初始狀態(tài)
increment: () => set((state) => ({ count: state.count + 1 })), // 增加count的函數(shù)
decrement: () => set((state) => ({ count: state.count - 1 })), // 減少count的函數(shù)
}));
create函數(shù)接受一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)接受一個(gè)set函數(shù)作為參數(shù),用于更新狀態(tài)。在這個(gè)回調(diào)函數(shù)中,定義了一個(gè)count狀態(tài)和兩個(gè)更新函數(shù)increment和decrement。
使用 Store:在組件中,可以使用自定義的 Hooks(上面的useStore)來獲取狀態(tài)和更新函數(shù),并在組件中使用它們。
import React from 'react';
import { useStore } from './store';
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
}
下面是一個(gè)較復(fù)雜的狀態(tài)管理:
import create from 'zustand';
const useUserStore = create((set) => ({
user: {
name: '',
age: 0,
email: ''
},
setUserName: (newName) => set((state) => ({ user: {...state.user, name: newName } })),
setUserAge: (newAge) => set((state) => ({ user: {...state.user, age: newAge } })),
setUserEmail: (newEmail) => set((state) => ({ user: {...state.user, email: newEmail } }))
}));
訂閱特定狀態(tài)片段
在 Zustand 中,如果有一個(gè)包含多個(gè)狀態(tài)的store,但在組件中只需要訂閱其中一個(gè)狀態(tài),可以通過解構(gòu)賦值從useStore返回的完整狀態(tài)對(duì)象中提取需要的狀態(tài)。Zustand的智能選擇器功能允許這樣做,而不會(huì)導(dǎo)致不必要的重新渲染。下面來看個(gè)簡(jiǎn)單的例子。
在這個(gè)store中,有兩個(gè)狀態(tài):count和name,以及兩個(gè)更新這些狀態(tài)的函數(shù)。
// store.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
name: 'Zustand Store',
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (newName) => set({ name: newName }),
}));
export default useStore;
現(xiàn)在,在組件中,如果只想訂閱count狀態(tài),可以這樣做:
// MyComponent.js
import React from 'react';
import useStore from './store';
function MyComponent() {
// 使用解構(gòu)賦值從store狀態(tài)中提取count
const { count } = useStore((state) => ({ count: state.count }));
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default MyComponent;
在組件中,傳遞了一個(gè)選擇器函數(shù)給useStore。這個(gè)選擇器函數(shù)接受當(dāng)前的store狀態(tài)作為參數(shù),并返回需要的部分狀態(tài)(在這個(gè)例子中是count)。這樣,Zustand就知道只需要在count狀態(tài)變化時(shí)通知這個(gè)組件。
如果想要訂閱多個(gè)狀態(tài),但不想訂閱全部狀態(tài),可以在選擇器函數(shù)中返回多個(gè)狀態(tài):
const { count, name } = useStore((state) => ({ count: state.count, name: state.name }));
使用中間件
Zustand 支持中間件,可以通過中間件來擴(kuò)展其功能。例如,可以使用內(nèi)置的persist中間件將狀態(tài)保存到本地存儲(chǔ),或者使用devtools中間件在瀏覽器的 Redux DevTools 擴(kuò)展中查看和調(diào)試 Zustand store 的狀態(tài)變化等。
在下面的例子中,使用persist中間件將count狀態(tài)保存到瀏覽器的 localStorage 中:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useCounterStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}),
{
name: 'counter-storage', // 本地存儲(chǔ)的key
}
)
);
我們還可以根據(jù)需求來自定義中間件,格式如下:
const myCustomMiddleware = (config) => (set, get, api) => {
// 預(yù)處理邏輯
const result = config(set, get, api); // 調(diào)用原始配置
// 后處理邏輯
return result;
};
然后,可以像這樣來應(yīng)用自定義中間件:
import { create } from 'zustand';
const createStore = (set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
});
const useStore = create(myCustomMiddleware(createStore));
export default useStore;
如果想同時(shí)應(yīng)用多個(gè)中間件,可以直接將它們鏈?zhǔn)秸{(diào)用:
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(
devtools(persist(createStore, { name: 'counter-storage' }), { name: 'my-counter-store' })
);
export default useStore;
異步支持
Zustand 默認(rèn)支持異步操作,最直接的方式是在創(chuàng)建 store 的時(shí)候定義異步函數(shù)??梢韵穸x同步動(dòng)作一樣定義異步動(dòng)作,只需確保它們返回 Promise。
import { create } from 'zustand';
const useStore = create((set) => ({
data: null,
loading: false,
error: null,
fetchData: async () => {
set({ loading: true, error: null }); // 設(shè)置加載狀態(tài)
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
set({ data, loading: false }); // 更新狀態(tài)為成功
} catch (err) {
set({ error: err.message, loading: false }); // 更新狀態(tài)為失敗
}
},
}));
export default useStore;