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