Zustand 使用優(yōu)化:關于自動生成選擇器
Zustand[1] 是目前 React 生態(tài)里比較受歡迎的一個狀態(tài)庫,主要是因為用法上的簡潔。
Zustand 簡單使用
首先安裝 zustand:
# NPM
npm install zustand
# Or, use any package manager of your choice.
接著從 zustand 庫中引入 create API 就能創(chuàng)建同時包含狀態(tài)和用于修改狀態(tài)的方法的 Store 對象了。
import { create } from 'zustand'
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))
這里,create() 接受一個回調函數用于定義初始 Store 中包含的內容:
- bears 是狀態(tài)
- increasePopulation()、removeAllBears()、updateBears() 則是用于修改 bears 這個狀態(tài)的方法,又叫 Action
同時,create() 返回的 useStore() 是一個 React Hook。useBearStore() 接收的是一個用于從 Store 中提取內容的回調函數,又叫“選擇器(Selector)”。
接下來,就可以在你的組件中使用 useBearStore() 了。
你可以引入狀態(tài):
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
圖片
也可以引入用于修改狀態(tài)的方法:
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
如此一來,你在 <Controls /> 中調用修改 increasePopulation() 后,會觸發(fā) state.bears 的值加 1,接著就能在 <BearCounter /> 中看到新的值了。
圖片
掌握了以上關于 Zustand 的基本用法后,其實你就可以開發(fā)項目了。
不過,你想更進一步提升開發(fā)體驗,那么就要去解決這個過程當中的一些使用痛點。
其中一個是關于更新嵌套狀態(tài)的,這在之前的文章《React 狀態(tài)庫 Zustand 入門教程》[2] 中有提到,有興趣的讀者可以移步閱讀。
不過,我們本次關注的是另一個痛點:就是從 Store 中獲取狀態(tài)/Action的過程。
自動生成選擇器
按照之前的介紹,在創(chuàng)建完 Store 之后,我們每次都要在組件中這樣去使用:
const bears = useBearStore((state) => state.bears)
我們要使用這種方式從 Store 中提取狀態(tài)或是 Action。
不過每次頻繁這樣去寫這樣一個選擇器函數是很乏味的,這個時候我們就可以考慮借助一個工具函數,對我們的 Store 進行增強,支持狀態(tài)/Action的快捷訪問。
這就是我們要介紹的 createSelectors(store) 函數了——先亮代碼:
const createSelectors = (store) => {
store.use = {}
for (let k of Object.keys(store.getState())) {
store.use[k] = () => store((s) => s[k])
}
return store
}
代碼量并不多,也好理解。
createSelectors() 接收的 store 就是前一節(jié)的 useBearStore,也就是 create() 的返回值。
createSelectors() 的作用很簡單,就是向 store 中添加一個 .use 屬性,用于快捷訪問其上的內容。
當然,這里有一個隱藏的點,就是可以通過 store.getState() 拿到當前 Store 的所有內容。
圖片
接著,修改之前的內容——為了便于區(qū)分,我們將原來的 useBearStore 該名稱 useBearStoreBase 了。經 createSelectors() 處理后,返回的是 useBearStore。
- const useBearStore = create((set) => ({
+ const useBearStoreBase = create((set) => ({
// ...
}))
+ const useBearStore = createSelectors(useBearStoreBase)
現在,修改組件中使用 useBearStore 的地方。
function BearCounter() {
- const bears = useBearStore((state) => state.bears)
+ const bears = useBearStore.use.bears()
return <h1>{bears} around here...</h1>
}
function Controls() {
- const increasePopulation = useBearStore((state) => state.increasePopulation)
+ const increasePopulation = useBearStore.use.increasePopulation()
return <button notallow={increasePopulation}>one up</button>
}
減少了一些代碼量,但是積少成多,也會提升一些開發(fā)體驗。
圖片
不過需要注意的是,每次使用 .use 獲取不管是狀態(tài)還是 Acton 時,都要帶上 () 的調用后綴。
// 不管是狀態(tài)還是 Action,后面都要帶上 `()`
const bears = useBearStore.use.bears()
const increasePopulation = useBearStore.use.increasePopulation()
當然,現在項目中大都使用 TypeScript,為了獲得更好的類型提示,我們對 createSelectors() 進行改造,添加類型注解。
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S,
) => {
let store = _store as WithSelectors<typeof _store>
store.use = {}
for (let k of Object.keys(store.getState())) {
;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
這也是官方給出的方案[3],如果你對 TypeScript 不夠熟悉也沒關系,直接將上述代碼貼到項目中使用即可。
總結
本文我們講解了在使用 Zustand 時的一個小優(yōu)化,關于自動生成選擇器。借助 createSelectors(),我們可以更加輕松、快捷的訪問 Store 中的狀態(tài)或是 Action。
好了,希望本文的內容對你的工作有所幫助。感謝閱讀,再見。
參考資料
[1]Zustand: https://docs.pmnd.rs/zustand
[2]《React 狀態(tài)庫 Zustand 入門教程》: https://juejin.cn/post/7388064351504056335#heading-4
[3]官方給出的方案: https://docs.pmnd.rs/zustand/guides/auto-generating-selectors#create-the-following-function:-createselectors