我们参考文章 React 开发 - 初始化项目 来创建一个名为 zustand-demo
的项目。并进行初始化的改造👇
zustand
是个小巧,快速和可扩展的 React
应用程序的状态管理的库。它有以下的优点:
- 简单的
API
- 最小的模版代码
- 更好的性能
- 支持
TypeScript
- 中间件支持
假设现在我们已经安装了 zustand
- yarn add zustand
。我们来介绍如何使用它👇
创建 store
我们的 store
就是一个 hook
。我们可以在里面放置基本类型数据,对象,函数。状态必须以不可变的方式更改,并且 set
函数会合并状态。
tsx
// src/stores/bearStore.tsx
import { create } from 'zustand'
type TBearStoreState = {
bears: number;
increasePopulation: () => void;
removeAllBears: () => void;
}
export const useBearStore = create<TBearStoreState>((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
然后我们来验证下:
tsx
// components/BearBox.tsx
import React from "react";
// 引入 useBearStore 这个hook
import { useBearStore } from "../stores/bearStore";
export const BearBox = () => {
// 获取到状态中的 bears
const bears = useBearStore(state => state.bears);
// 增加
const increasePopulation = useBearStore(state => state.increasePopulation);
// 删除
const removeAllBears = useBearStore(state => state.removeAllBears);
return (
<div>
<h1>Bear Box</h1>
<p>bears: {bears}</p>
<div>
<button onClick={increasePopulation}>Add Bear</button>
<button onClick={removeAllBears}>Remove Bear</button>
</div>
</div>
);
}
get() 和 set()
这里我们介绍 get()
和 set()
这两个函数。
我们创建另外一个 store
来了解👇 set()
用来更改状态, get()
获取获获取状态。
tsx
// src/stores/catStore.tsx
import { create } from "zustand";
type TCatStoreState = {
cats: {
bigCats: number;
};
increaseBigCats: () => void;
}
export const useCatStore = create<TCatStoreState>() ((set) => ({
cats: {
bigCats: 0,
},
increaseBigCats: () => {
set(
(state) => ({
// set 函数会自动帮我们合并第一层级的状态,但是第二层的状态就需要我们使用 ... 扩展符来提取状态
cats: {
...state.cats,
bigCats: state.cats.bigCats + 1
}
})
)
}
}))
我们在上面的 set
函数中,更改了 cats
中的状态。set
函数会自动帮我合并第一层级的状态,但是其他层级就需要自己手动更改。
有时候,我们需要在 set
函数之外 获取到 state
的数据,那么我们就可以使用 get
函数。如下另外一种获取 Big Cats
的方法:
tsx
// src/stores/catStore.tsx
type TCatStoreState = {
// ...
getBigCatsOutSideSetFn: () => void;
}
export const useCatStore = create<TCatStoreState>() ((set, get) => ({
cats: {
bigCats: 0,
smallCats: 0,
},
// 通过 get() 函数在 set() 函数外获取 state 的状态
getBigCatsOutSideSetFn: () => {
const bigCatsNumber = get().cats.bigCats;
return `There are ${bigCatsNumber} big cats.`;
}
}))
使用 immer 更改状态
在上面我们也知道,**set** 函数会自动帮我合并第一层级的状态
,其他的状态需要我们手动调整。如果状态是有多层级的数据,那么,我们将望而止步。还好,immer
这个中间件能够帮助我们实现这个功能。
通过 yarn add immer
来安装依赖。
我们在原先的案例中更改:
tsx
// src/stores/catStore.tsx
import { create } from "zustand";
type TCatStoreState = {
cats: {
smallCats: {
name: string;
// other
};
bigCats: number;
// other
};
increaseBigCats: () => void;
}
export const useCatStore = create<TCatStoreState>() ((set) => ({
cats: {
bigCats: 0,
smallCats: {
name: "hello kitty"
},
},
increaseBigCats: () => {
set(
(state) => ({
cats: {
...state.cats,
bigCats: state.cats.bigCats + 1,
smallCats: {
...state.cats.smallCats,
name: "hello jimmy"
}
}
})
)
}
}))
没有使用 immer
之前,我们需要对更改的层级进行 ...
扩展符进行提取。引入之后,就简单了,我们只需要更改关注的值就行了:
tsx
// src/stores/catStore.tsx
import { immer } from "zustand/middleware/immer";
export const useCatStore = create<TCatStoreState>() (immer((set) => ({
cats: {
bigCats: 0,
smallCats: {
name: "hello kitty"
},
},
increaseBigCats: () => {
set(
(state) => ({
cats: {
bigCats: state.cats.bigCats + 1,
smallCats: {
name: "hello jimmy"
}
}
})
)
}
})))
使用 selector
seletor
允许我们在调用 set()
函数的时候,只更新状态的一部份,而不是整个状态的对象。这有助于提高性能。
比如,一个页面两个组件中,一个组件调用了 cat 中的
smallCats
,另一个组件中国调用了bigCats
,但是我们在更改smallCats
中的状态时候,却影响到了另一个组件带有bigCats
的组件重新渲染。这是因为之前我们在更改smallCats
的时候,更改了全部状态数据cats
我们参考 Auto Generating Selectors 来创建 Selectors
。
typescript
// src/utils/createSelector.ts
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
export const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S,
) => {
const store = _store as WithSelectors<typeof _store>
store.use = {}
for (const k of Object.keys(store.getState())) {
(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
结合,我们将 selector
整合到 store
上,这里以 catStore
为例:
tsx
// src/stores/catStore.tsx
import { createSelectors } from "../utils/createSelector";
// ...
export const useCatStore = createSelectors(
create<TCatStoreState>() (immer((set) => ({
cats: {
bigCats: 0,
smallCats: {
name: "hello kitty"
},
},
increaseBigCats: () => {
set(
(state) => ({
cats: {
bigCats: state.cats.bigCats + 1,
smallCats: {
name: "hello jimmy"
}
}
})
)
}
})))
);
然后,我们就可以通过 useCatStore.use.
来调用。比如,只是出发点更改 increaseBigCats
:
tsx
// src/components/CatBox.tsx
// const increateBigCats = useCatStore(state => state.increaseBigCats);
const increateBigCats = useCatStore.use.increaseBigCats();
return (
<div>
<h1>Cat Box</h1>
<p>big cats: {bigCats} - {name}</p>
<div>
<button onClick={increateBigCats}>Add Big Cat</button>
</div>
</div>
);
此时,页面中我们只是更改了 big cats
,其他没有使用到 big cats
的组件不会重新渲染。
嗯,zustand
的相关用法就聊到这里,感谢阅读【✅】🌹