
zustand是一个小巧、快速、可扩展的状态管理解决方案。它提供了基于hooks的API。
德语中“ZUSTAND”如何发音?多查几次吧,不要念错了。


像react-hooks项目那样,先使用npm create vite@latest,创建react+ts的项目,然后对项目的一些文件进行处理。
运行如下的装包命令:
xxxxxxxxxx51# NPM2npm install zustand34# Yarn5yarn add zustand
创建store的方法是调用zustand提供的create函数。使用zustand创建出来的store是一个hook函数,因此推荐以useXxx的方式为store命名。语法格式如下:
xxxxxxxxxx131// 1.按需导入create函数2import { create } from "zustand"34// 2.调用create函数,创建名为useStore的hook5const useStore = create((set, get) => {6 // 2.1 return出去的是store中的数据或方法,是全局共享的7 return {8 bear: 09 }10})1112// 3.向外导出useStore的hook13export default useStore在src下创建store文件夹,创建index.ts文件,然后写上面的代码。
1、在组件的tsx文件中,导入store对应的hook:
xxxxxxxxxx21// 1.导入useStore这个hook2import useStore from "@/store"2、在需要使用数据的组件中,调用useStore函数,通过传入的selector获取当前组件需要的数据:
xxxxxxxxxx21// 2.调用hook,通过selector获取数据2const bears = useStore((state) => state.bears)3、通过selector获取到的数据,可以在组件中进行渲染使用:
xxxxxxxxxx111import { FC } from 'react'2import useStore from '@/store'34export const Father: FC = () => {5 // 通过selector获取数据6 const bears = useStore(state => state.bears)7 return <>8 <h3>Father组件</h3>9 <p>小熊的数量是:{bears}</p>10 </>11}在components文件夹中创建setup.tsx文件,写入上面的代码,并将组件导入到App.tsx组件中进行展示。

1、在vite-env.d.ts中添加store的类型声明:
这个文件中声明的类型是全局可用的,所以如果在某个文件中使用这里面的类型,就不需要导入即可使用。
xxxxxxxxxx51/// <reference types="vite/client" />23type BearType = {4 bears:number5}2、在src/store/index.ts中调用create函数时,使用类型声明:
xxxxxxxxxx101import { create } from "zustand";23const useStore = create<BearType>()(() => {4 return {5 bears: 0,6 };7});89export default useStore;103、改造完成后,在组件中使用selector选择器获取数据时,就有了TS的类型提示信息。
注意:请注意
create<T>()后面额外的(),详细原因请查看microsoft/TypeScript#10571
1、在vite-env.d.ts中为BearType添加名为incrementBear的属性,它是一个函数,用来让bears的数量自增+1,示例代码如下:
xxxxxxxxxx61/// <reference types="vite/client" />2type BearType = {3 bears: number;4 incrementBears: () => void;5};62、在src/store/index.ts中新增incrementBears函数如下:
xxxxxxxxxx141import { create } from "zustand";23// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据4const useStore = create<BearType>()((set) => {5 // 向外暴露出store的数据或方法6 return {7 bears: 0,8 // 让小熊数量自增+1。注意:set函数里面是一个函数,参数是state,当返回值是一个对象的时候,同名的属性会重新计算值,会以最后一个值为主,于是达到更新值的目的。注意这里的形参state,其实真实意思是prevState,是旧值,所以这种方法可以自增加1。那么set函数也可以直接放一个对象进去赋值,比如说:set({bears:9}),这样bears值就会被更新为9,不过这种方式不常用,还是用函数的方式是最好的。9 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),10 };11});1213export default useStore;143、在Son1组件中,调用useStore并配合selector获取到需要的函数,并绑定为按钮的点击事件处理函数:
xxxxxxxxxx121const Son1: FC = () => {2 // 基于selector获取到需要的函数3 const incrementBears = useStore(state => state.incrementBears)45 return (6 <>7 <h5>Son1子组件</h5>8 {/* 绑定为按钮的点击事件处理函数 */}9 <button onClick={incrementBears}>bears+1</button>10 </>11 )12}
1、在vite-env.d.ts中为BearType添加名为resetBears的属性,它是一个函数,用来把bears的数量重置为0,示例代码如下:
xxxxxxxxxx71/// <reference types="vite/client" />2type BearType = {3 bears: number;4 incrementBears: () => void;5 resetBears: () => void;6};72、在src/store/index.ts中新增reset函数如下:
xxxxxxxxxx161import { create } from "zustand";23// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据4const useStore = create<BearType>()((set) => {5 // 向外暴露出store的数据或方法6 return {7 bears: 0,8 // 让小熊数量自增+19 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),10 // 重置bears的数量11 resetBears: () => set(() => ({ bears: 0 })),12 };13});1415export default useStore;163、在Son2组件中,调用useStore并配合selector获取到需要的函数,并进行使用:
xxxxxxxxxx111const Son2: FC = () => {2 // 基于selector获取到需要的函数3 const resetBears = useStore(state => state.resetBears)4 return (5 <>6 <h5>Son2子组件</h5>7 {/* 直接把函数绑定为按钮的点击事件处理函数 */}8 <button onClick={resetBears}>重置bears</button>9 </>10 )11}
1、在vite-env.d.ts中为BearType添加名为decrementBearsByStep的属性,它是一个函数,接收一个可选的step步长值,从而让bears的数量自减,示例代码如下:
xxxxxxxxxx81/// <reference types="vite/client" />2type BearType = {3 bears: number;4 incrementBears: () => void;5 resetBears: () => void;6 decrementBearsByStep: (step?: number) => void;7};82、在src/store/index.ts中新增decrementBearsByStep函数如下:
xxxxxxxxxx181import { create } from "zustand";23// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据4const useStore = create<BearType>()((set) => {5 // 向外暴露出store的数据或方法6 return {7 bears: 0,8 // 让小熊数量自增+19 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),10 // 重置bears的数量11 resetBears: () => set(() => ({ bears: 0 })),12 // 根据step步长值让bears数量自减13 decrementBearsByStep: (step = 1) => set((prevState) => ({ bears: prevState.bears - step })),14 };15});1617export default useStore;183、在Son3组件中,调用useStore并配合selector获取到需要的函数,并进行使用:
xxxxxxxxxx141const Son3:FC = () => { 2 // 基于selector获取到需要的函数3 const decrementBearsByStep = useStore(state => state.decrementBearsByStep)45 const decrement = () => {6 decrementBearsByStep(5)7 }8 return (9 <>10 <h5>Son5子组件</h5>11 <button onClick={decrement}>自减-5</button>12 </>13 )14 }
1、在vite-env.d.ts中为BearType添加名为asyncIncrementBears的属性,它是一个函数,用来延迟1秒后让数值自增+1,示例代码如下:
xxxxxxxxxx91/// <reference types="vite/client" />2type BearType = {3 bears: number;4 incrementBears: () => void;5 resetBears: () => void;6 decrementBearsByStep: (step: number) => void;7 asyncIncrementBears: () => void;8};92、在src/store/index.ts中新增asyncIncrementBears函数如下:
xxxxxxxxxx241import { create } from "zustand";23// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据4const useStore = create<BearType>()((set, get) => {5 // 向外暴露出store的数据或方法6 return {7 bears: 0,8 // 让小熊数量自增+19 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),10 // 重置bears的数量11 resetBears: () => set(() => ({ bears: 0 })),12 // 根据step步长值让bears数量自减13 decrementBearsByStep: (step = 1) => set((prevState) => ({ bears: prevState.bears - step })),14 // 延迟1秒后,让bears数量+115 asyncIncrementBears: () => {16 setTimeout(() => {17 // 注意这里 get() 方法的调用,它可以获取到 store 对象,并访问 store 中的数据或方法。当然也可以使用set()函数,但是不方便。18 get().incrementBears();19 }, 1000);20 },21 };22});2324export default useStore;3、在Son1组件中添加1秒后bears+1的按钮,并绑定为按钮的点击事件处理函数:
xxxxxxxxxx141const Son1: FC = () => {2 // 基于selector获取到需要的函数3 const incrementBears = useStore(state => state.incrementBears)4 const asyncIncrementBears = useStore(state => state.asyncIncrementBears)56 return (7 <>8 <h5>Son1子组件</h5>9 {/* 绑定为按钮的点击事件处理函数 */}10 <button onClick={incrementBears}>bears+1</button>11 <button onClick={asyncIncrementBears}>1秒后bears+1</button>12 </>13 )14}
1、修改vite-env.d.ts中的BearType类型,添加fishes相关的数据和方法:
xxxxxxxxxx161/// <reference types="vite/client" />23type BearType = {4 // bears相关的数据和方法5 bears: number;6 incrementBears: () => void;7 resetBears: () => void;8 decrementBearsByStep: (step?: number) => void;9 asyncIncrementBears: () => void;1011 // fishes相关的数据和方法12 fishes: number;13 incrementFishes: () => void;14 resetFishes: () => void;15};162、修改@/store/index.ts模块,添加fishes相关的数据和方法:
xxxxxxxxxx341import { create } from "zustand";23// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据4const useStore = create<BearType>()((set, get) => {5 // 向外暴露出store的数据或方法6 return {7 // bears相关的数据和方法8 bears: 0,9 // 让小熊数量自增+110 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),11 // 重置bears的数量12 resetBears: () => set(() => ({ bears: 0 })),13 // 根据step步长值让bears数量自减14 decrementBearsByStep: (step = 1) => set((prevState) => ({ bears: prevState.bears - step })),15 // 延迟1秒后,让bears数量+116 asyncIncrementBears: () => {17 setTimeout(() => {18 // 注意这里 get() 方法的调用,它可以获取到 store 对象,并访问 store 中的数据或方法。19 get().incrementBears();20 }, 1000);21 },2223 // fishes相关的数据和方法24 // 小鱼干的数量25 fishes: 0,26 // 小鱼干自增+127 incrementFishes: () => set((prevState) => ({ fishes: prevState.fishes + 1 })),28 // 重置小鱼干的数量29 resetFishes: () => set(() => ({ fishes: 0 })),30 };31});3233export default useStore;343、在@/components/目录下新建fishes.tsx模块,在模块中创建名为Fishes的组件:
xxxxxxxxxx181import { FC } from 'react'2import useStore from '@/store'34export const Fishes: FC = () => {5 // 基于selector获取需要的数据或方法6 const fishes = useStore(state => state.fishes)7 const incrementFishes = useStore(state => state.incrementFishes)8 const resetFishes = useStore(state => state.resetFishes)910 return (11 <>12 <h3>小鱼干的数量:{fishes}</h3>13 {/* 绑定点击事件处理函数 */}14 <button onClick={incrementFishes}>fishes+1</button>15 <button onClick={resetFishes}>重置小鱼干的数量</button>16 </>17 )18}4、在@/App.tsx中,导入并使用Fishes组件:
xxxxxxxxxx121import { Father } from '@/components/setup'2import { Fishes } from '@/components/Fishes'34const App = () => {5 return (<>6 <Father />7 <hr />8 <Fishes />9 </>)10}1112export default App;
随着项目规模的扩大,存储在store中的数据会变得越来越难以维护。此时,可以考虑把单一的全局store进行拆分。在zustand中提供了两种拆分store的方式:
1、Multi-Store:把不同的数据和方法,拆分为多个彼此独立的store
2、Single-Store:把不同的数据和方法,拆分为多个slice切片,最终,把多个slice合并成全局唯一的store。
注意:上面两种拆分Store的方式都可以完美运行,在您自己的项目中,可以依据个人喜好自行决定使用哪种拆分方式。
为了分别为大家演示上述的两种Store拆分方式,我们需要借助于Git对项目进行版本管理:
1、初始化Git仓库,并提交初始版本:
xxxxxxxxxx31git init2git add .3git commit -m '初始版本-未拆分Store'2、基于master分支,分别创建两个分支,用来单独演示每种拆分方式的具体过程:
xxxxxxxxxx51# 基于此分支,演示 Multi-Store 的拆分方式2git checkout -b multi-store34# 基于此分支,演示 Single-Store 的拆分方式5git checkout -b single-store3、切换到multi-store分支,先为大家演示Multi-Store的拆分过程:
xxxxxxxxxx11git checkout multi-store上面的git操作不涉及到仓库,因为从始至终没有push操作,但是却很好的完成了分支的目的,真的是前所未有的体验。很好。
Multi-Store指的是:把不同的数据和方法,拆分为多个彼此独立的store。
1、修改vite-env.d.ts文件,把BearType和FishType拆分为两个独立的类型声明:
xxxxxxxxxx181/// <reference types="vite/client" />23// bears相关的数据和方法4type BearType = {5 bears: number;6 incrementBears: () => void;7 resetBears: () => void;8 decrementBearsByStep: (step?: number) => void;9 asyncIncrementBears: () => void;10};1112// fishes相关的数据和方法13type FishType = {14 fishes: number;15 incrementFishes: () => void;16 resetFishes: () => void;17};182、在@/store目录下,新建bearStore.ts模块,用来声明bears相关的Store数据。然后把@/store/index.ts中的代码粘贴到bearStore.ts中进行修改(特别注意:要把useStore更名为useBearStore):
xxxxxxxxxx281// @/store/bearStore.ts23import { create } from "zustand";45// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据6const useBearStore = create<BearType>()((set, get) => {7 // 向外暴露出store的数据或方法8 return {9 // bears相关的数据和方法10 bears: 0,11 // 让小熊数量自增+112 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),13 // 重置bears的数量14 resetBears: () => set(() => ({ bears: 0 })),15 // 根据step步长值让bears数量自减16 decrementBearsByStep: (step = 1) => set((prevState) => ({ bears: prevState.bears - step })),17 // 延迟1秒后,让bears数量+118 asyncIncrementBears: () => {19 setTimeout(() => {20 // 注意这里 get() 方法的调用,它可以获取到 store 对象,并访问 store 中的数据或方法。21 get().incrementBears();22 }, 1000);23 },24 };25});2627export default useBearStore;283、在@/store目录下,新建fishStore.ts模块,用来声明fishes相关的Store数据。然后把@/store/index.ts中的代码粘贴到fishStore.ts中进行修改(特别注意:要把useStore更名为useFishStore):
xxxxxxxxxx201// @/store/fishStore.ts23import { create } from "zustand";45// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据6const useFishStore = create<FishType>()((set) => {7 // 向外暴露出store的数据或方法8 return {9 // fishes相关的数据和方法10 // 小鱼干的数量11 fishes: 0,12 // 小鱼干自增+113 incrementFishes: () => set((prevState) => ({ fishes: prevState.fishes + 1 })),14 // 重置小鱼干的数量15 resetFishes: () => set(() => ({ fishes: 0 })),16 };17});1819export default useFishStore;204、删除@/store/index.ts模块。
5、改造@/components/setup.tsx模块,把import useStore from "@/store"的模块导入代码改为import useBearStore from "@/store/bearStore",并将所有用到useStore的地方更名为useBearStore。
6、改造@/components/Fishes.tsx模块,把import useStore from "@/store"的模块导入代码改为import useFishStore from "@/store/fishStore",并将所有用到useStore的地方更名为useFishStore。
运行没有任何问题。这个用起来很简单啊,比pinia还要简单,不需要最终合并为一个文件。
zustand内置了数据持久化的persist中间件,对于Multi-Store中的每个Store,我们可以自行决定是否对其进行持久化存储。例如,下面的代码演示了如何对bearStore进行持久化:
xxxxxxxxxx381import { create } from "zustand";2// 1、导入需要的中间件3import { persist } from "zustand/middleware";45// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据6const useBearStore = create<BearType>()(7 // 2、对当前 Store 中的数据进行持久化存储8 persist(9 (set, get) => {10 // 向外暴露出store的数据或方法11 return {12 // bears相关的数据和方法13 bears: 0,14 // 让小熊数量自增+115 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),16 // 重置bears的数量17 resetBears: () => set(() => ({ bears: 0 })),18 // 根据step步长值让bears数量自减19 decrementBearsByStep: (step = 1) => set((prevState) => ({ bears: prevState.bears - step })),20 // 延迟1秒后,让bears数量+121 asyncIncrementBears: () => {22 setTimeout(() => {23 // 注意这里 get() 方法的调用,它可以获取到 store 对象,并访问 store 中的数据或方法。24 get().incrementBears();25 }, 1000);26 },27 };28 },29 // 3、必须提供一个 persist 的配置对象30 {31 // name 用来指定存储后的数据名称32 name: "bear-store",33 }34 )35);3637export default useBearStore;38
注意看右侧local storage里面值的变化,并且浏览器刷新之后,bears状态没有改变,说明值被持久化存储了。
默认情况下,数据会被持久化到localStorage中。如果想自定义存储的位置,可以借助于zustand的createJSONStorage中间件来进行配置。例如,下面的代码演示了如何把数据持久化存储到sessionStorage中:
xxxxxxxxxx281import { create } from "zustand";2import { persist, createJSONStorage } from "zustand/middleware";34// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据5const useFishStore = create<FishType>()(6 persist(7 (set) => {8 // 向外暴露出store的数据或方法9 return {10 // fishes相关的数据和方法11 // 小鱼干的数量12 fishes: 0,13 // 小鱼干自增+114 incrementFishes: () => set((prevState) => ({ fishes: prevState.fishes + 1 })),15 // 重置小鱼干的数量16 resetFishes: () => set(() => ({ fishes: 0 })),17 };18 },19 {20 name: "fish-store",21 // storage用来自定义存储的位置。这里的sessionStorage是怎么来的?这就是WebApi,可以直接使用。22 storage: createJSONStorage(() => sessionStorage),23 }24 )25);2627export default useFishStore;28
注意看右侧session storage里面值的变化,并且浏览器刷新之后,值也没有变化,被持久化保存了。
小技巧:
在vscode中,如果鼠标选择一个函数或方法很困难,不知道结束的括号在哪里,可以使用快捷键来操作,鼠标的光标选中一个函数名或将光标放在方法的开头括号处,按两次alt+shift+rightArrow,就可以选中这个函数或方法了,这样准确得多。
前提:浏览器安装并启用了Redux DevTools
1、从zustand/middleware中,按需导入devtools中间件:
xxxxxxxxxx11import { persist, createJSONStorage, devtools } from "zustand/middleware";2、在create()中调用devtools()中间件:
xxxxxxxxxx311import { create } from "zustand";2import { persist, createJSONStorage, devtools } from "zustand/middleware";34// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据5const useFishStore = create<FishType>()(6 // 在Redux DevTools中调试当前store中的数据7 devtools(8 persist(9 (set) => {10 // 向外暴露出store的数据或方法11 return {12 // fishes相关的数据和方法13 // 小鱼干的数量14 fishes: 0,15 // 小鱼干自增+116 incrementFishes: () => set((prevState) => ({ fishes: prevState.fishes + 1 })),17 // 重置小鱼干的数量18 resetFishes: () => set(() => ({ fishes: 0 })),19 };20 },21 {22 name: "fish-store",23 // storage用来自定义存储的位置24 storage: createJSONStorage(() => sessionStorage),25 }26 )27 )28);2930export default useFishStore;31
3、另外,devtools中间件还允许提供一个可选的配置对象,可通过name属性指定store在调试框中显示的名称:
xxxxxxxxxx11devtools(参数1,{name:'fishStore'})xxxxxxxxxx341import { create } from "zustand";2import { persist, createJSONStorage, devtools } from "zustand/middleware";34// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据5const useFishStore = create<FishType>()(6 // 在Redux DevTools中调试当前store中的数据7 devtools(8 persist(9 (set) => {10 // 向外暴露出store的数据或方法11 return {12 // fishes相关的数据和方法13 // 小鱼干的数量14 fishes: 0,15 // 小鱼干自增+116 incrementFishes: () => set((prevState) => ({ fishes: prevState.fishes + 1 })),17 // 重置小鱼干的数量18 resetFishes: () => set(() => ({ fishes: 0 })),19 };20 },21 {22 name: "fish-store",23 // storage用来自定义存储的位置24 storage: createJSONStorage(() => sessionStorage),25 }26 ),27 {28 name: "fishStore",29 }30 )31);3233export default useFishStore;34

如果是复杂结构的数据,更改起来会非常麻烦,使用immer可以简化数据的操作,复杂数据的纯函数写法我还真的没有写过。
如果文件里面导入了immer,可以使用immer的方式来修改数据,但按照以前的方式来修改数据也是没有问题的,都是兼容的。比如说重置方法:set({bears:0}),如果使用immer的写法,需要写成这样:set(prevState => {prevState.bears = 0}),反而更麻烦,所以具体按照哪种方法来修改数据,要看情况。
1、运行如下命令,安装immer依赖包:
xxxxxxxxxx21# 注意 immer 是 zustand 的内置依赖项,只需要安装到项目中即可。这是什么意思?意思是zustand里面用到了immer(肯定是有immer才起作用,没有安装immer的话,zustand里面的immer就不使用。所以刚开始使用zustand,没有安装immer也不会报错。),直接拿zustand的immer相关函数过来使用即可, immer 这个第三方库不需要单独导入,而是通过zustand里面的immer来导入。2npm i immer -S2、按需导入 immer 中间件:
xxxxxxxxxx11import {immer} from "zustand/middleware/immer"3、在create()中,调用immer()中间件:
xxxxxxxxxx311import { create } from "zustand";2// 1、导入需要的中间件3import { persist } from "zustand/middleware";4// 4、按需导入 immer 中间件5import { immer } from "zustand/middleware/immer";67// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据8const useBearStore = create<BearType>()(9 // 5、使用 immer 简化数据的变更操作10 immer(11 // 2、对当前 Store 中的数据进行持久化存储12 persist(13 (set, get) => {14 // 向外暴露出store的数据或方法15 return {16 // bears相关的数据和方法17 bears: 0,18 // .......省略其它的方法,在下一步中会详细写明19 };20 },21 // 3、必须提供一个 persist 的配置对象22 {23 // name 用来指定存储后的数据名称24 name: "bear-store",25 }26 )27 )28);2930export default useBearStore;314、基于 immer 的语法,简化数据的变更操作。在set(fn)的fn回调函数中,可以直接修改原数据对象。下面的代码是基于 immer 语法修改后的 actions 函数:
xxxxxxxxxx551import { create } from "zustand";2// 1、导入需要的中间件3import { persist } from "zustand/middleware";4// 4、按需导入 immer 中间件5import { immer } from "zustand/middleware/immer";67// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据8const useBearStore = create<BearType>()(9 // 5、使用 immer 简化数据的变更操作10 immer(11 // 2、对当前 Store 中的数据进行持久化存储12 persist(13 (set, get) => {14 // 向外暴露出store的数据或方法15 return {16 // bears相关的数据和方法17 bears: 0,18 // 让小熊数量自增+119 incrementBears: () =>20 set((prevState) => {21 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来22 prevState.bears += 1;23 }),24 // 重置bears的数量25 resetBears: () =>26 set((prevState) => {27 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来28 prevState.bears = 0;29 }),30 // 根据step步长值让bears数量自减31 decrementBearsByStep: (step = 1) =>32 set((prevState) => {33 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来34 prevState.bears -= step;35 }),36 // 延迟1秒后,让bears数量+137 asyncIncrementBears: () => {38 setTimeout(() => {39 // 注意这里 get() 方法的调用,它可以获取到 store 对象,并访问 store 中的数据或方法。40 get().incrementBears();41 }, 1000);42 },43 };44 },45 // 3、必须提供一个 persist 的配置对象46 {47 // name 用来指定存储后的数据名称48 name: "bear-store",49 }50 )51 )52);5354export default useBearStore;55效果:

原文地址请参考:Zustand TypeScript Guide - using-middlewares
以下内容节选自 zustand 官方文档:
Also, we recommend using
devtoolsmiddleware as last as possible. For example, when you use it withimmeras a middleware, it should beimmer(devtools(...))and notdevtools(immer(...)). This is becausedevtoolsmutates thesetStateand adds a type parameter on it, which could get lost if other middlewares (likeimmer) also mutatesetStatebeforedevtools. Hence usingdevtoolsat the end makes sure that no middlewares mutatesetStatebefore it.
翻译成中文,大概意思是:
此外,我们建议尽可能最后使用 devtools 中间件。例如,当您将它与 immer 一起用作中间件时,它应该是immer(devtools(...))而不是devtools(immer(...))。
这是因为 devtools 改变了 setState ,并在其上添加了一个类型参数,如果其它中间件(如immer)也在devtools之前改变了setState,那么这个参数可能会丢失。因此,在最后使用 devtools 可以确保没有中间件在它之前改变 setState。
需要先配置persist中间件,再配置devtools中间件,最后配置immer中间件。最终改造完成的代码如下:
xxxxxxxxxx411import { create } from "zustand";2import { persist, createJSONStorage, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";45// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据6const useFishStore = create<FishType>()(7 // 简化变更数据的操作,用到immer中间件8 immer(9 // 在Redux DevTools中调试当前store中的数据10 devtools(11 persist(12 (set) => {13 // 向外暴露出store的数据或方法14 return {15 // fishes相关的数据和方法16 // 小鱼干的数量17 fishes: 0,18 // 小鱼干自增+119 incrementFishes: () =>20 set((prevState) => {21 prevState.fishes = 0;22 }),23 // 重置小鱼干的数量24 resetFishes: () => set({ fishes: 0 }),25 };26 },27 {28 name: "fish-store",29 // storage用来自定义存储的位置30 storage: createJSONStorage(() => sessionStorage),31 }32 ),33 {34 name: "fishStore",35 }36 )37 )38);3940export default useFishStore;41效果:

目前在bearStore和fishStore中,数据和函数定义在一起,随着项目规模的扩大,每个Store中的结构会显得比较混乱。我们可以把Action函数从Store的create()中抽离出来,使代码结构更加清晰。
1、修改vite-env.d.ts文件中的FishType类型,把它下面所有的Action函数的类型全部注释或删除掉:
xxxxxxxxxx51type FishType = {2 fishes: number;3 // incrementFishes: () => void;4 // resetFishes: () => void;5};2、修改@/store/fishStore.ts模块,把create()中的Action函数单独抽离出来,放到与useFishStore同层级的区域,并且暴露出去让组件使用:
用到了store的setState方法。
xxxxxxxxxx451import { create } from "zustand";2import { persist, createJSONStorage, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";45// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据6const useFishStore = create<FishType>()(7 // 简化变更数据的操作,用到immer中间件8 immer(9 // 在Redux DevTools中调试当前store中的数据10 devtools(11 persist(12 () => {13 // 向外暴露出store的数据或方法14 return {15 // 小鱼干的数量16 fishes: 0,17 };18 },19 {20 name: "fish-store",21 // storage用来自定义存储的位置22 storage: createJSONStorage(() => sessionStorage),23 }24 ),25 {26 name: "fishStore",27 }28 )29 )30);3132// 小鱼干自增+133export const incrementFishes = () => {34 useFishStore.setState((prevState) => {35 prevState.fishes += 1;36 });37};3839// 重置小鱼干的数量40export const resetFishes = () => {41 useFishStore.setState({ fishes: 0 });42};4344export default useFishStore;453、改造@/components/Fishes.tsx中的代码如下:
xxxxxxxxxx171import { FC } from 'react'2// 导入具体方法的方式需要更改3import useFishStore, { incrementFishes, resetFishes } from '@/store/fishStore'45export const Fishes: FC = () => {6 // 基于selector获取需要的数据或方法7 const fishes = useFishStore(state => state.fishes)89 return (10 <>11 <h3>小鱼干的数量:{fishes}</h3>12 {/* 绑定点击事件处理函数 */}13 <button onClick={incrementFishes}>fishes+1</button>14 <button onClick={resetFishes}>重置小鱼干的数量</button>15 </>16 )17}
1、修改vite-env.d.ts文件中的BearType类型,把它下面所有的Action函数的类型注释或删除掉:
xxxxxxxxxx71type BearType = {2 bears: number;3// incrementBears: () => void;4// resetBears: () => void;5// decrementBearsByStep: (step?: number) => void;6// asyncIncrementBears: () => void;7};2、修改@/store/bearState.ts模块,把create()中的Action函数单独抽离出来:
xxxxxxxxxx621import { create } from "zustand";2// 1、导入需要的中间件3import { persist, devtools } from "zustand/middleware";4// 4、按需导入 immer 中间件5import { immer } from "zustand/middleware/immer";67// 这里的set是一个函数,是zustand对外提供的一个参数,用来修改store中的数据8const useBearStore = create<BearType>()(9 // 5、使用 immer 简化数据的变更操作10 immer(11 devtools(12 // 2、对当前 Store 中的数据进行持久化存储13 persist(14 () => {15 // 向外暴露出store的数据16 return {17 bears: 0,18 };19 },20 // 3、必须提供一个 persist 的配置对象21 {22 // name 用来指定存储后的数据名称23 name: "bear-store",24 }25 ),26 {27 name: "bear-store",28 }29 )30 )31);3233// 让小熊数量自增+134export const incrementBears = () =>35 useBearStore.setState((prevState) => {36 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来37 prevState.bears += 1;38 });3940// 重置bears的数量41export const resetBears = () =>42 useBearStore.setState((prevState) => {43 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来44 prevState.bears = 0;45 });4647// 根据step步长值让bears数量自减48export const decrementBearsByStep = (step = 1) =>49 useBearStore.setState((prevState) => {50 // ☆☆☆ 请注意这里的 {} ,改用 immer 语法后,必须使用 {} 把修改数据的代码包裹起来51 prevState.bears -= step;52 });5354// 延迟1秒后,让bears数量+155export const asyncIncrementBears = () => {56 setTimeout(() => {57 incrementBears();58 }, 1000);59};6061export default useBearStore;623、改造@/components/setup.tsx中的代码如下:
xxxxxxxxxx501import { FC } from 'react'2import useBearStore, { incrementBears, resetBears, asyncIncrementBears, decrementBearsByStep } from '@/store/bearStore'34export const Father: FC = () => {56 const bears = useBearStore(state => state.bears)7 return <>8 <h3>Father组件</h3>9 <p>小熊的数量是:{bears}</p>10 <hr />11 <Son1 />12 <hr />13 <Son2 />14 <hr />15 <Son3 />16 </>17}1819const Son1: FC = () => {20 return (21 <>22 <h5>Son1子组件</h5>23 {/* 绑定为按钮的点击事件处理函数 */}24 <button onClick={incrementBears}>bears+1</button>25 <button onClick={asyncIncrementBears}>1秒后bears+1</button>26 </>27 )28}2930const Son2: FC = () => {31 return (32 <>33 <h5>Son2子组件</h5>34 {/* 直接把函数绑定为按钮的点击事件处理函数 */}35 <button onClick={resetBears}>重置bears</button>36 </>37 )38}3940const Son3: FC = () => {41 const decrement = () => {42 decrementBearsByStep(5)43 }44 return (45 <>46 <h5>Son5子组件</h5>47 <button onClick={decrement}>自减-5</button>48 </>49 )50}运行效果如下:

将所有store的重置函数都执行一遍,就可以重置所有store中的数据了。
xxxxxxxxxx251// @/components/Fishes.tsx23import type { FC } from 'react'4import useFishStore, { incrementFishes, resetFishes } from '@/store/fishStore'5import { resetBears } from '@/store/bearStore'67export const Fishes: FC = () => {8 // 基于selector获取需要的数据或方法9 const fishes = useFishStore(state => state.fishes)1011 const resetAllStore = () => {12 resetFishes();13 resetBears();14 }1516 return (17 <>18 <h3>小鱼干的数量:{fishes}</h3>19 {/* 绑定点击事件处理函数 */}20 <button onClick={incrementFishes}>fishes+1</button>21 <button onClick={resetFishes}>重置小鱼干的数量</button>22 <button onClick={resetAllStore}>重置所有store中的数据</button>23 </>24 )25}效果:

1、在@/store目录下新建tools/文件夹,并在@/store/tools目录下,新建resetters.ts模块,代码如下:
xxxxxxxxxx81// (() => void)[] 是resetters 的类型,即:2// resetters 是一个数组3// resetters 是一个函数的数组,每个函数的类型为 () => void45const resetters: (() => void)[] = [];67// 默认导出 resetters 数组8export default resetters;2、修改@/store/bearStore.ts模块,按照如下4个步骤提供初始数据和添加 resetter 函数。核心代码如下:
xxxxxxxxxx561import { create } from "zustand";2import { persist, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";4// 3、导入 resetters 数组5import resetters from "@/store/tools/resetters";67// 1、定义 bearStore 的初始数据对象8const initBearState = {9 bears: 0,10};1112const useBearStore = create<BearType>()(13 immer(14 devtools(15 persist(16 (set) => {17 // 4、把 bearStore 的resetter函数添加到 resetters 数组中18 resetters.push(() => set(initBearState));19 return {20 // 2、为 store 提供初始数据21 initBearState,22 };23 },24 {25 name: "bear-store",26 }27 ),28 {29 name: "bear-store",30 }31 )32 )33);3435export const incrementBears = () =>36 useBearStore.setState((prevState) => {37 prevState.bears += 1;38 });3940export const resetBears = () =>41 useBearStore.setState((prevState) => {42 prevState.bears = 0;43 });4445export const decrementBearsByStep = (step = 1) =>46 useBearStore.setState((prevState) => {47 prevState.bears -= step;48 });4950export const asyncIncrementBears = () => {51 setTimeout(() => {52 incrementBears();53 }, 1000);54};5556export default useBearStore;3、修改@/store/fishStore.ts模块,按照如下4个步骤提供初始数据和添加 resetter 函数。核心代码如下:
xxxxxxxxxx461import { create } from "zustand";2import { persist, createJSONStorage, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";4// 3、导入 resetters 数组5import resetters from "@/store/tools/resetters";67// 1、定义 fishStore 的初始数据对象8const initFishState = {9 fishes: 0,10};1112const useFishStore = create<FishType>()(13 immer(14 devtools(15 persist(16 (set) => {17 // 4、把 fishStore 的resetter函数添加到 resetters 数组中18 resetters.push(() => set(initFishState));19 return {20 // 2、为 store 提供初始数据21 initFishState,22 };23 },24 {25 name: "fish-store",26 storage: createJSONStorage(() => sessionStorage),27 }28 ),29 {30 name: "fishStore",31 }32 )33 )34);3536export const incrementFishes = () => {37 useFishStore.setState((prevState) => {38 prevState.fishes += 1;39 });40};4142export const resetFishes = () => {43 useFishStore.setState({ fishes: 0 });44};4546export default useFishStore;4、修改@/store/tools/resetters.ts中的代码,向外按需导出一个名为resetAllStore的函数:
xxxxxxxxxx111// (() => void)[] 是resetters 的类型,即:2// resetters 是一个数组3// resetters 是一个函数的数组,每个函数的类型为 () => void45const resetters: (() => void)[] = [];67// 默认导出 resetters 数组8export default resetters;910// 按需导出名为 resetAllStore 的函数,调用此函数,会重置所有 store 中的数据11export const resetAllStore = () => resetters.forEach((resetFn) => resetFn());5、测试重置所有store的功能
xxxxxxxxxx191import { FC } from 'react'2import useFishStore, { incrementFishes, resetFishes } from '@/store/fishStore'3// 导入重置ALL函数4import { resetAllStore } from '@/store/tools/resetters'56export const Fishes: FC = () => {7 // 基于selector获取需要的数据或方法8 const fishes = useFishStore(state => state.fishes)910 return (11 <>12 <h3>小鱼干的数量:{fishes}</h3>13 {/* 绑定点击事件处理函数 */}14 <button onClick={incrementFishes}>fishes+1</button>15 <button onClick={resetFishes}>重置小鱼干的数量</button>16 <button onClick={resetAllStore}>重置所有store中的数据</button>17 </>18 )19}效果:

在react组件中,可以基于 store hook 的 selector 选择器,轻松获取并使用 store 中的数据,例如:
xxxxxxxxxx111import { FC } from 'react'2import useBearStore from '@/store/bearStore'34export const Father: FC = () => {5 // 调用 useBearStore ,同时提供一个选择数据的选择器6 const bears = useBearStore(state => state.bears)7 return <>8 <h3>Father组件</h3>9 <p>小熊的数量是:{bears}</p>10 </>11}而在实际开发中,我们经常需要在组件之外的地方访问store中的数据,例如:在 axios 的拦截器中访问 store 中的 token 等其它数据。此时,可以使用 store hook 的 getState()方法拿到 store 的数据对象,并访问具体的数据,语法格式如下:
xxxxxxxxxx71// 1、导入 store 中的 hook2import useBearStore from "@/store/bearStore"3import useFishStore from "@/store/fishStore"45// 2、调用 getState() 方法,获取到 store 的数据对象6useBearStore.getState().bears7useFishStore.getState().fishes例如,在 axios 的请求拦截器中,为请求头挂载 bears 的数量:
xxxxxxxxxx111// 导入 store 的 hook2import useBearStore from "@/store/bearStore"34// 请求拦截器5axios.interceptors.request.use(function(config){6 // 为请求头挂载 bears 的数量7 config.headers.bears = useBearStore.getState().bears8 return config9}, function(error){10 return Promise.reject(error)11})注意:在组件之外访问 store 的 Actions,只需按需导入对应的 Actions 函数即可使用。
1、修改vite-env.d.ts模块,新增familyType类型:
xxxxxxxxxx71type familyType = {2 family: {3 father: string;4 mother: string;5 son: string;6 };7};2、在@/store目录下,新建familyStore.ts模块如下:
xxxxxxxxxx201import { create } from "zustand";23// 初始数据4const initFamilyState: familyType = {5 family: {6 father: "Skyler",7 mother: "Walt",8 son: "Walter Junior",9 },10};1112// 创建store的hook13const useFamilyStore = create<familyType>()(() => {14 return {15 initFamilyState,16 };17});1819// 导出store的hook20export default useFamilyStore;
1、配置persist中间件
xxxxxxxxxx301import { create } from "zustand";2// 导入persist中间件3import { persist } from "zustand/middleware";45// 初始数据6const initFamilyState: familyType = {7 family: {8 father: "Skyler",9 mother: "Walt",10 son: "Walter Junior",11 },12};1314// 创建store的hook15const useFamilyStore = create<familyType>()(16 // 配置persist中间件17 persist(18 () => {19 return {20 initFamilyState,21 };22 },23 {24 name: "family-store",25 }26 )27);2829// 导出store的hook30export default useFamilyStore;2、配置devtools中间件
xxxxxxxxxx351import { create } from "zustand";2// 导入 devtools 中间件3import { persist, devtools } from "zustand/middleware";45// 初始数据6const initFamilyState: familyType = {7 family: {8 father: "Skyler",9 mother: "Walt",10 son: "Walter Junior",11 },12};1314// 创建store的hook15const useFamilyStore = create<familyType>()(16 // 配置 devtools 中间件17 devtools(18 persist(19 () => {20 return {21 initFamilyState,22 };23 },24 {25 name: "family-store",26 }27 ),28 {29 name: "familyStore",30 }31 )32);3334// 导出store的hook35export default useFamilyStore;3、配置immer中间件
xxxxxxxxxx381import { create } from "zustand";2import { persist, devtools } from "zustand/middleware";3// 导入immer中间件4import { immer } from "zustand/middleware/immer";56// 初始数据7const initFamilyState: familyType = {8 family: {9 father: "Skyler",10 mother: "Walt",11 son: "Walter Junior",12 },13};1415// 创建store的hook16const useFamilyStore = create<familyType>()(17 // 配置 immer 中间件18 immer(19 devtools(20 persist(21 () => {22 return {23 initFamilyState,24 };25 },26 {27 name: "family-store",28 }29 ),30 {31 name: "familyStore",32 }33 )34 )35);3637// 导出store的hook38export default useFamilyStore;1、在@/components目录下,新建family.tsx模块,并创建名为FamilyWrapper、FamilyMembers、FamilyNames的3个组件:
xxxxxxxxxx211import { FC } from 'react'23export const FamilyWrapper: FC = () => {4 return <>5 <FamilyMembers />6 <hr />7 <FamilyNames />8 </>9}1011const FamilyMembers: FC = () => {12 return <>13 <h5>小熊的家庭成员:</h5>14 </>15}1617const FamilyNames: FC = () => {18 return <>19 <h5>熊熊们的名字是:</h5>20 </>21}2、在@/App.tsx根组件中,按需导入并使用FamilyWrapper组件:
xxxxxxxxxx151import { Father } from '@/components/setup'2import { Fishes } from '@/components/Fishes'3import { FamilyWrapper } from '@/components/family'45const App = () => {6 return (<>7 <Father />8 <hr />9 <Fishes />10 <hr />11 <FamilyWrapper />12 </>)13}1415export default App;3、在@/components/family.tsx中,按需导入useFamilyStore的hook,并使用:
xxxxxxxxxx261import { FC } from 'react'2import useFamilyStore from '@/store/familyStore'34export const FamilyWrapper: FC = () => {5 return <>6 <FamilyMembers />7 <hr />8 <FamilyNames />9 </>10}1112const FamilyMembers: FC = () => {13 const members = useFamilyStore(state => Object.keys(state.family))1415 return <>16 <h5>小熊的家庭成员:{members.join(", ")}</h5>17 </>18}1920const FamilyNames: FC = () => {21 const names = useFamilyStore(state => Object.values(state.family))2223 return <>24 <h5>熊熊们的名字是:{names.join(", ")}</h5>25 </>26}显示效果:

1、修改@/store/familyStore.ts模块,新增updateSonName的Action函数:
xxxxxxxxxx451import { create } from "zustand";2import { persist, devtools } from "zustand/middleware";3// 导入immer中间件4import { immer } from "zustand/middleware/immer";56// 初始数据7const initFamilyState: familyType = {8 family: {9 father: "Skyler",10 mother: "Walt",11 son: "Walter Junior",12 },13};1415// 创建store的hook16const useFamilyStore = create<familyType>()(17 // 配置 immer 中间件18 immer(19 devtools(20 persist(21 () => {22 return {23 initFamilyState,24 };25 },26 {27 name: "family-store",28 }29 ),30 {31 name: "familyStore",32 }33 )34 )35);3637// 修改son的名字38export const updateSonName = (sonName: string) => {39 useFamilyStore.setState((state) => {40 state.family.son = sonName;41 });42};4344// 导出store的hook45export default useFamilyStore;2、修改@/components/family.tsx模块下的FamilyNames组件,新增修改son的名字的button按钮:
xxxxxxxxxx271import { FC } from 'react'2import useFamilyStore, { updateSonName } from '@/store/familyStore'34export const FamilyWrapper: FC = () => {5 return <>6 <FamilyMembers />7 <hr />8 <FamilyNames />9 </>10}1112const FamilyMembers: FC = () => {13 const members = useFamilyStore(state => Object.keys(state.family))1415 return <>16 <h5>小熊的家庭成员:{members.join(", ")}</h5>17 </>18}1920const FamilyNames: FC = () => {21 const names = useFamilyStore(state => Object.values(state.family))2223 return <>24 <h5>熊熊们的名字是:{names.join(", ")}</h5>25 <button onClick={() => updateSonName("zaizai")}>修改son的名字</button>26 </>27}显示效果:

1、在@/store/familyStore.ts模块中导入resetters并使用:
xxxxxxxxxx441import { create } from "zustand";2import { persist, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";4// 导入 resetters5import resetters from "./tools/resetters";67// 初始数据8const initFamilyState: familyType = {9 family: {10 father: "Skyler",11 mother: "Walt",12 son: "Walter Junior",13 },14};1516const useFamilyStore = create<familyType>()(17 immer(18 devtools(19 persist(20 (set) => {21 // 将重置方法添加到resetters中22 resetters.push(() => set(initFamilyState));23 return {24 initFamilyState,25 };26 },27 {28 name: "family-store",29 }30 ),31 {32 name: "familyStore",33 }34 )35 )36);3738export const updateSonName = (sonName: string) => {39 useFamilyStore.setState((state) => {40 state.family.son = sonName;41 });42};4344export default useFamilyStore;2、由于重置所有state的按钮是绑定在fish组件上的,所以点击这个按钮,查看效果:

1、修改@/vite-env.d.ts文件中的FamilyType的类型定义,新增daughter属性:
xxxxxxxxxx81type familyType = {2 family: {3 father: string;4 mother: string;5 son: string;6 daughter?: string;7 };8};2、修改@/store/familyStore.ts模块,新增名为addDaughterName的Action函数:
xxxxxxxxxx501import { create } from "zustand";2import { persist, devtools } from "zustand/middleware";3import { immer } from "zustand/middleware/immer";4// 导入 resetters5import resetters from "./tools/resetters";67// 初始数据8const initFamilyState: familyType = {9 family: {10 father: "Skyler",11 mother: "Walt",12 son: "Walter Junior",13 },14};1516const useFamilyStore = create<familyType>()(17 immer(18 devtools(19 persist(20 (set) => {21 // 将重置方法添加到resetters中22 resetters.push(() => set(initFamilyState));23 return {24 initFamilyState,25 };26 },27 {28 name: "family-store",29 }30 ),31 {32 name: "familyStore",33 }34 )35 )36);3738export const updateSonName = (sonName: string) => {39 useFamilyStore.setState((state) => {40 state.family.son = sonName;41 });42};4344export const addDaughterName = (daughterName: string) => {45 useFamilyStore.setState((state) => {46 state.family.daughter = daughterName;47 });48};4950export default useFamilyStore;3、修改@/components/family.tsx模块,按需导入addDaughterName函数,并使用:
xxxxxxxxxx281import { FC } from 'react'2import useFamilyStore, { updateSonName, addDaughterName } from '@/store/familyStore'34export const FamilyWrapper: FC = () => {5 return <>6 <FamilyMembers />7 <hr />8 <FamilyNames />9 </>10}1112const FamilyMembers: FC = () => {13 const members = useFamilyStore(state => Object.keys(state.family))1415 return <>16 <h5>小熊的家庭成员:{members.join(", ")}</h5>17 <button onClick={() => addDaughterName("lily")}>添加daughter</button>18 </>19}2021const FamilyNames: FC = () => {22 const names = useFamilyStore(state => Object.values(state.family))2324 return <>25 <h5>熊熊们的名字是:{names.join(", ")}</h5>26 <button onClick={() => updateSonName("zaizai")}>修改son的名字</button>27 </>28}查看效果:

1、在FamilyMembers组件中,添加useEffect的调用,用来监视组件的render渲染:
xxxxxxxxxx141import { FC, useEffect } from 'react'23const FamilyMembers: FC = () => {4 const members = useFamilyStore(state => Object.keys(state.family))56 useEffect(() => {7 console.log('触发了 FamilyMembers 组件的渲染')8 })910 return <>11 <h5>小熊的家庭成员:{members.join(", ")}</h5>12 </>13}14此时,当组件首次渲染或更新渲染时,都会触发useEffect回调函数的执行。

FamilyMembers组件在添加daughter时,是要更新,但是在更新son名字的时候,是没必要更新的。
2、当我们点击FamilyNames组件中的修改son的名字按钮时,并没有为family对象添加任何新成员,但是触发了FamilyMembers组件的更新渲染,这就导致了性能的浪费。此时,我们可以使用useShallow这个zustand hook帮助我们优化渲染的性能:在更新前后,如果selector获取到的数据没有任何变化,则会防止组件的更新渲染,从而提升组件的渲染性能:
xxxxxxxxxx21// 先从 zustand/react/shallow 模块下,按需导入 useShallow 这个 hook2import { useShallow } from "zustand/react/shallow"把需要进行性能优化的selector包裹在useShallow中即可:
xxxxxxxxxx11const members = useFamilyStore(useShallow(state => Object.keys(state.family)))完整代码如下:
xxxxxxxxxx141import { useShallow } from "zustand/react/shallow"23const FamilyMembers: FC = () => {4 const members = useFamilyStore(useShallow(state => Object.keys(state.family)))56 useEffect(() => {7 console.log('member组件渲染了')8 })910 return <>11 <h5>家庭成员是:</h5>12 <p>{members.join(", ")}</p>13 </>14}修改完成后,再次点击修改son的名字按钮时,不会导致FamilyMembers组件的更新渲染,因为更新前后的members数组没有任何变化。只有点击添加daughter按钮时,才会触发FamilyMembers组件的更新渲染,因为此时的members数组发生了变化。

在zustand中,subscribe(fn)可以用来订阅 Store 数据的变化,并在数据变化后执行 fn 回调函数。subscribe()是Store的一个方法。
在回调函数中,接收两个形参newValue和oldValue,其中:
同时,subscribe()还返回一个取消订阅的函数。语法格式如下:
xxxxxxxxxx41// useStore 表示具体的store,你想订阅哪个store的消息,就使用哪个store。 unsubFn 用来接收subscribe返回的取消订阅的函数。2const unsubFn = useStore.subscribe((newValue,oldValue) => {3 console.log(newValue,oldValue)4})
1、修改@/components/family.tsx中的FamilyNames组件,基于subscribe()订阅familyStore数据的变化:
xxxxxxxxxx191const FamilyNames: FC = () => {2 const names = useFamilyStore(state => Object.values(state.family))34 useEffect(() => {5 // 订阅 Store 中数据的变化。注意:这里的unsubFn是subscribe订阅函数的返回值,作用是一个清理函数,那么在组件卸载的时候,就会执行这个清理函数,来取消订阅。6 const unsubFn = useFamilyStore.subscribe((newValue, oldValue) => {7 console.log(`son的新值是:${newValue.family.son},旧值是:${oldValue.family.son}`)8 })910 // 清理函数:组件卸载时,取消订阅11 return () => unsubFn();12 }, [])1314 return <>15 <h5>熊熊们的名字是:{names.join(", ")}</h5>16 <button onClick={() => updateSonName("zaizai")}>修改son的名字</button>17 <button onClick={() => addDaughterName("lily")}>添加daughter</button>18 </>19}执行效果:

注意:
订阅函数在useEffect中只需要订阅一次即可,所以useEffect的deps为空数组。
2、点击button按钮,取消订阅:
第一步的代码里面,由于没有写组件卸载的代码,所以在这里添加一个button,专门用来取消订阅。使用了useRef来存储“取消函数”。
xxxxxxxxxx261const FamilyNames: FC = () => {2 const names = useFamilyStore(state => Object.values(state.family))3 // 1、使用 ref 在渲染周期内,存储“取消订阅的函数”4 const ref = useRef<() => void>()56 useEffect(() => {7 // 订阅 Store 中数据的变化。注意:这里的unsubFn是订阅函数的返回值,作用是一个清理函数,那么在组件卸载的时候,就会执行这个清理函数,来取消订阅。8 const unsubFn = useFamilyStore.subscribe((newValue, oldValue) => {9 console.log(`son的新值是:${newValue.family.son},旧值是:${oldValue.family.son}`)10 })1112 // 2、将 unsubFn 存储到 ref.current 中13 ref.current = unsubFn1415 // 清理函数:组件卸载时,取消订阅16 return () => unsubFn();17 }, [])1819 return <>20 <h5>熊熊们的名字是:{names.join(", ")}</h5>21 <button onClick={() => updateSonName("zaizai")}>修改son的名字</button>22 <button onClick={() => addDaughterName("lily")}>添加daughter</button>23 {/* 3、点击按钮,取消订阅 */}24 <button onClick={() => ref.current && ref.current()}>取消订阅</button>25 </>26}执行效果:

subscribe的缺点:只能订阅整个 Store 数据的变化,无法订阅 Store 下某个具体数据的变化。
点击“添加daughter”按钮,仍然会触发订阅函数,我的本意可能只是监听son值的变化,但是监听的是整个store,执行效果从上面的动图中可以看到。
要想监听具体数据的变化,需要用到下面的
subscribeWithSelector。
基于subscribeWithSelector这个中间件,可以订阅(监听)Store中指定数据的变化。它的使用分为以下两个主要步骤:
1、导入subscribeWithSelector中间件,并在创建 Store 的 hook 中使用此中间件:

语法规则:
xxxxxxxxxx71// 按需导入中间件2import { subscribeWithSelector } from "zustand/middleware"34const useState = create(5 // 使用中间件。这里要看懂,没有看到return啊,这里返回的是对象,是简写方式,要用()包裹。6 subscribeWithSelector(() => ({name:"zs",age:20}))7)2、调用subscribe()函数,订阅具体数据的变化:
xxxxxxxxxx11const unsubFn = useStore.subscribe(selectorFn, cb, options?)例如:
xxxxxxxxxx41// 这里监听的是 state.age 的变化2const unsubFn = useStore.subscribe(state => state.age, (newAge, oldAge) => {3 console.log(newAge, oldAge);4},{ fireImmediately:true })其中options配置对象中的fireImmediately:true表示立即触发一次回调函数的执行。
1、导入subscribeWithSelector中间件,并在创建 Store 的 hook 中使用此中间件:
xxxxxxxxxx521import { create } from "zustand";2// 导入 subscribeWithSelector 中间件3import { persist, devtools, subscribeWithSelector } from "zustand/middleware";4import { immer } from "zustand/middleware/immer";5import { resetters } from "./tools/resetters";67const initFamilyState: familyType = {8 family: {9 father: "Jack",10 mother: "lucy",11 son: "Jack Chen",12 },13};1415const useFamilyStore = create<familyType>()(16 // 使用中间件,调用顺序看官方文档,按照要求做即可。17 subscribeWithSelector(18 immer(19 devtools(20 persist(21 (set) => {22 resetters.push(() => set(initFamilyState));23 return {24 initFamilyState,25 };26 },27 {28 name: "family-store",29 }30 ),31 {32 name: "familyStore",33 }34 )35 )36 )37);3839export const updateSonName = (sonName: string) => {40 useFamilyStore.setState((prevState) => {41 prevState.family.son = sonName;42 });43};4445export const addDaughterName = (name: string) => {46 useFamilyStore.setState((prevState) => {47 prevState.family.daughter = name;48 });49};5051export default useFamilyStore;522、调用subscribe()函数,订阅具体数据的变化:
xxxxxxxxxx241const FamilyNames: FC = () => {2 const names = useFamilyStore(state => Object.values(state.family))34 const MyRef = useRef<() => void>();56 useEffect(() => {7 // 调用 subscribe 方法,三个参数分别写出来。8 const unsubFn = useFamilyStore.subscribe(state => state.family.son, (newValue, oldValue) => {9 console.log(`新值是:${newValue},旧值是:${oldValue}`)10 }, { fireImmediately: true })1112 MyRef.current = unsubFn;1314 return () => unsubFn();15 }, [])1617 return <>18 <h5>家庭成员姓名是:</h5>19 <p>{names.join(", ")}</p>20 <button onClick={() => updateSonName('仔仔')}>更改son姓名</button>21 <button onClick={() => addDaughterName('梨园')}>添加daughter</button>22 <button onClick={() => MyRef.current && MyRef.current()}>卸载组件</button>23 </>24}执行效果:

需求:如果小鱼干的数量>= 5,则让 Father 组件的背景色为 lightgreen;否则,让Father组件的背景色为lightgray。
1、使用subscribeWithSelector中间件,更改fishStore.ts模块:
xxxxxxxxxx461import { create } from "zustand";2// 导入 subscribeWithSelector 中间件3import { persist, createJSONStorage, devtools, subscribeWithSelector } from "zustand/middleware";4import { immer } from "zustand/middleware/immer";5import resetters from "@/store/tools/resetters";67const initFishState = {8 fishes: 0,9};1011const useFishStore = create<FishType>()(12 // 使用 subscribeWithSelector 中间件13 subscribeWithSelector(14 immer(15 devtools(16 persist(17 (set) => {18 resetters.push(() => set(initFishState));19 return {20 initFishState,21 };22 },23 {24 name: "fish-store",25 storage: createJSONStorage(() => sessionStorage),26 }27 ),28 {29 name: "fishStore",30 }31 )32 )33 )34);3536export const incrementFishes = () => {37 useFishStore.setState((prevState) => {38 prevState.fishes += 1;39 });40};4142export const resetFishes = () => {43 useFishStore.setState({ fishes: 0 });44};4546export default useFishStore;2、改造@/components/setup.tsx模块中的Father组件,结合useState、useEffect和zustand的subscribe,实现背景色变换的功能:
xxxxxxxxxx381import { FC, useEffect, useState } from 'react'2import useBearStore, { incrementBears, resetBears, asyncIncrementBears, decrementBearsByStep } from '@/store/bearStore'3import useFishStore from '@/store/fishStore'45export const Father: FC = () => {67 const bears = useBearStore(state => state.bears)8 // 1、定义 state 状态,值为背景颜色9 const [bgColor, setBgColor] = useState<'lightgreen' | 'lightgray'>('lightgray')1011 useEffect(() => {12 // 3、订阅 fishes 数量的变化,动态地为 bgColor 赋值13 const unsubFn = useFishStore.subscribe(state => state.fishes, (newValue) => {14 if (newValue >= 5) {15 setBgColor('lightgreen')16 } else {17 setBgColor('lightgray')18 }19 }, { fireImmediately: true })2021 // 4、返回一个清理函数22 return () => unsubFn()23 }, [])2425 // 2、为 div 绑定 style 样式26 return <div style={{ padding: 10, borderRadius: 5, background: bgColor }}>27 <h3>Father组件</h3>28 <p>小熊的数量是:{bears}</p>29 <hr />30 <Son1 />31 <hr />32 <Son2 />33 <hr />34 <Son3 />35 </div>36}3738// ...省略其它没有修改过的代码效果:

Single-Store指的是:把不同的数据和方法,拆分为多个slice切片,最终,把多个slice合并成全局唯一的Store。
注意:
“拆分成多个slice切片”,这些切片只是store的组成部分,并不是store。全局只有唯一的一个store。这一点要重点理解。
将项目切换到single-store分支,再进行下面的步骤。
1、将vite-env.d.ts中的BearType类型注释或删除掉,创建两个新的数据类型,分别是BearSliceType和FishSliceType。
xxxxxxxxxx151/// <reference types="vite/client" />23type BearSliceType = {4 bears: number;5 incrementBears: () => void;6 resetBears: () => void;7 decrementBearsByStep: (step?: number) => void;8 asyncIncrementBears: () => void;9};1011type FishSliceType = {12 fishes: number;13 incrementFishes: () => void;14 resetFishes: () => void;15};2、在@/store/目录下,新建slices/文件夹,并新建两个slice模块,分别是fishSlice.ts和bearSlice.ts。其中fishSlice.ts中的代码如下:
xxxxxxxxxx171// 1、导入 slice 的TS类型2import type { StateCreator } from "zustand";34// 2、声明 slice 函数。函数还是有 set 和 get 两个参数。5const createFishSlice: StateCreator<FishSliceType> = (set) => {6 return {7 // 小鱼干的数量8 fishes: 0,9 // 小鱼干自增+110 incrementFishes: () => set((state) => ({ fishes: state.fishes + 1 })),11 // 重置小鱼干的数量12 resetFishes: () => set({ fishes: 0 }),13 };14};1516// 3、向外导出 slice 函数17export default createFishSlice;bearSlice.ts中的代码如下:
xxxxxxxxxx201// 1、导入 slice 的TS类型2import { StateCreator } from "zustand";34// 2、声明 slice 函数5const createBearSlice: StateCreator<BearSliceType> = (set, get) => {6 return {7 bears: 0,8 incrementBears: () => set((state) => ({ bears: state.bears + 1 })),9 resetBears: () => set({ bears: 0 }),10 decrementBearsByStep: (step = 1) => set((state) => ({ bears: state.bears - step })),11 asyncIncrementBears: () => {12 setTimeout(() => {13 get().incrementBears();14 }, 1000);15 },16 };17};1819// 3、向外导出 slice 函数20export default createBearSlice;3、改造@/store/index.ts中的代码,导入多个slice切片,并进行组装:
xxxxxxxxxx141import { create } from "zustand";2// 1、导入多个 slice 的 create 函数3import createFishSlice from '@/store/slices/fishSlice'4import createBearSlice from '@/store/slices/bearSlice'56const useStore = create<BearSliceType & FishSliceType>()((arg) => {7 // 2、组装 slice 切片8 return {9 createFishSlice(arg),10 createBearSlice(arg)11 };12});1314export default useStore;这样就可以直接使用useStore了,setup.tsx和Fishes.tsx里面的代码不需要进行更改。
效果:

1、修改vite-env.d.ts中的TS类型,把Action相关的类型删除掉:
xxxxxxxxxx91/// <reference types="vite/client" />23type BearSliceType = {4 bears: number;5};67type FishSliceType = {8 fishes: number;9};2、修改@/store/slices/fishSlice.ts模块如下:
xxxxxxxxxx201import { StateCreator } from "zustand";23// 导入全局唯一的store4import useStore from "../index";56const createFishSlice: StateCreator<FishSliceType> = (set) => {7 return {8 // 小鱼干的数量9 fishes: 0,10 };11};1213// 使用useStore的setState方法来修改值14// 小鱼干自增+115export const incrementFishes = () => useStore.setState((state) => ({ fishes: state.fishes + 1 }));1617// 重置小鱼干的数量18export const resetFishes = () => useStore.setState({ fishes: 0 });1920export default createFishSlice;3、修改@/store/slices/bearSlice.ts模块如下:
xxxxxxxxxx241import { StateCreator } from "zustand";23// 导入全局唯一的store4import useStore from "..";56const createBearSlice: StateCreator<BearSliceType> = () => {7 return {8 bears: 0,9 };10};1112export const incrementBears = () => useStore.setState((state) => ({ bears: state.bears + 1 }))1314export const resetBears = () => useStore.setState({ bears: 0 })1516export const decrementBearsByStep = (step = 1) => useStore.setState((state) => ({ bears: state.bears - step }))1718export const asyncIncrementBears = () => {19 setTimeout(() => {20 incrementBears();21 }, 1000);22}2324export default createBearSlice;这里其实我有一个疑问,引入的useStore没有问题吗?
没有问题,其实仔细想一想,暴露出去的方法,和useStore的关系是什么?
是暴露出去的方法需要useStore,而useStore不需要这些方法,而且暴露出去的方法是独立的暴露,所以用起来更没有问题。
每个slice文件里面的方法,完全可以定义在index.ts中,这样理解起来更加清楚,是这些方法需要useStore。
4、修改@/components/Fishes.tsx模块中的代码如下:
xxxxxxxxxx191import { FC } from 'react'2import useStore from '@/store'34// 导入需要的Action函数5import { incrementFishes, resetFishes } from '@/store/slices/fishSlice'67export const Fishes: FC = () => {8 // 基于selector获取需要的数据或方法9 const fishes = useStore(state => state.fishes)1011 return (12 <>13 <h3>小鱼干的数量:{fishes}</h3>14 {/* 绑定点击事件处理函数 */}15 <button onClick={incrementFishes}>fishes+1</button>16 <button onClick={resetFishes}>重置小鱼干的数量</button>17 </>18 )19}5、修改@/components/setup.tsx模块中的代码如下:
xxxxxxxxxx541import { FC } from 'react'2import useStore from '@/store'34// 导入需要的Action函数5import { incrementBears, resetBears, decrementBearsByStep, asyncIncrementBears } from '@/store/slices/bearSlice'67export const Father: FC = () => {8 const bears = useStore(state => state.bears)910 return <>11 <h3>Father组件</h3>12 <p>小熊的数量是:{bears}</p>13 <hr />14 <Son1 />15 <hr />16 <Son2 />17 <hr />18 <Son3 />19 </>20}2122const Son1: FC = () => {2324 return (25 <>26 <h5>Son1子组件</h5>27 {/* 绑定为按钮的点击事件处理函数 */}28 <button onClick={incrementBears}>bears+1</button>29 <button onClick={asyncIncrementBears}>1秒后bears+1</button>30 </>31 )32}3334const Son2: FC = () => {35 return (36 <>37 <h5>Son2子组件</h5>38 {/* 直接把函数绑定为按钮的点击事件处理函数 */}39 <button onClick={resetBears}>重置bears</button>40 </>41 )42}4344const Son3: FC = () => {45 const decrement = () => {46 decrementBearsByStep(5)47 }48 return (49 <>50 <h5>Son5子组件</h5>51 <button onClick={decrement}>自减-5</button>52 </>53 )54}展示效果没有问题。
1、在@/store/index.ts中,按需导入persist相关的中间件:
xxxxxxxxxx21// persist 默认存储到localStorage中,如果想要存储到sessionStorage中,则需要使用createJSONStorage方法2import { persist, createJSONStorage } from "zustand/middleware"
2、在调用create()期间,配置 persist 中间件:
xxxxxxxxxx251import { create } from "zustand";2import createFishSlice from "@/store/slices/fishSlice";3import createBearSlice from "@/store/slices/bearSlice";45// 导入persist相关中间件6import { persist, createJSONStorage } from "zustand/middleware";78const useStore = create<BearSliceType & FishSliceType>()(9 // 使用persist中间件10 persist(11 (arg) => {12 return {13 createFishSlice(arg),14 createBearSlice(arg),15 };16 },17 {18 // 持久化的配置项19 name: "store",20 storage: createJSONStorage(() => sessionStorage),21 }22 )23);2425export default useStore;效果:

可通过persist中间件的配置对象中的partialize选项,自定义要持久化Store中的哪些数据,它的语法格式如下:
xxxxxxxxxx71persist(参数1, {2 name:'store',3 partialize: (state) => {// 形参中的 state 是 store 中所有的数据4 // 对数据进行过滤、筛选等处理操作...5 return 要持久化的数据对象6 }7})示例代码如下:
xxxxxxxxxx331import { create } from "zustand";2import createFishSlice from "@/store/slices/fishSlice";3import createBearSlice from "@/store/slices/bearSlice";45// 导入persist相关中间件6import { persist, createJSONStorage } from "zustand/middleware";78const useStore = create<BearSliceType & FishSliceType>()(9 // 使用persist中间件10 persist(11 (arg) => {12 return {13 createFishSlice(arg),14 createBearSlice(arg),15 };16 },17 {18 // 持久化的配置项19 name: "store",20 storage: createJSONStorage(() => sessionStorage),21 partialize: (state) => {22 // 1、keys 数组中存储的,是要持久化的数据项的名字23 const keys = ["fishes", "bears"];24 // 2、从 state 中把不需要持久化的 entry 过滤掉,只保留需要持久化的 entries25 const selectedEntries = Object.entries(state).filter((entry) => keys.includes(entry[0]));26 // 3、根据过滤得到的 entries ,形成一个新对象并返回。这个返回的对象就是要被持久化的数据对象27 return Object.fromEntries(selectedEntries);28 },29 }30 )31);3233export default useStore;切换要持久化存储的数据,查看效果:
只存储fishes:

只存储bears:

Object.entries()方法相关知识:
partialize(state){},这个方法里面的state参数是什么?输出看一下:
可以看到是state里面的原始数据,返回值是什么呢?先看官方文档:
https://docs.pmnd.rs/zustand/integrations/persisting-store-data
可以看到,返回的应该是筛选后的对象,默认是返回state,处理的方法在上面已经说明了。
1、按需导入 devtools 中间件:
xxxxxxxxxx11import { devtools } from "zustand/middleware"2、在调用 create() 时,使用 devtools 中间件,并配置 store 在devtools中显示的名称:
xxxxxxxxxx351import { create } from "zustand";2import createFishSlice from "@/store/slices/fishSlice";3import createBearSlice from "@/store/slices/bearSlice";45// 导入 devtools 中间件6import { persist, createJSONStorage, devtools } from "zustand/middleware";78const useStore = create<BearSliceType & FishSliceType>()(9 // 使用 devtools 中间件10 devtools(11 persist(12 (arg) => {13 return {14 createFishSlice(arg),15 createBearSlice(arg),16 };17 },18 {19 name: "store",20 storage: createJSONStorage(() => sessionStorage),21 partialize: (state) => {22 const keys = ["fishes", "bears"];23 const selectedEntries = Object.entries(state).filter((entry) => keys.includes(entry[0]));24 return Object.fromEntries(selectedEntries);25 },26 }27 ),28 {29 // 配置 store 在 devtools 中的名称30 name: "appStore",31 }32 )33);3435export default useStore;效果:

1、安装 immer
xxxxxxxxxx21# 注意 immer 是 zustand 的内置依赖项,只需要安装到项目中即可。这是什么意思?意思是 immer 不需要单独导入,而是通过zustand里面的immer来导入。2npm i immer -S2、在@/store/index.ts模块中,按需导入immer中间件:
xxxxxxxxxx11import { immer } from "zustand/middleware/immer"并在调用create()期间,配置 immer 中间件:
xxxxxxxxxx371import { create } from "zustand";2import createFishSlice from "@/store/slices/fishSlice";3import createBearSlice from "@/store/slices/bearSlice";4import { persist, createJSONStorage, devtools } from "zustand/middleware";56// 导入 immer 中间件7import { immer } from "zustand/middleware/immer";89const useStore = create<BearSliceType & FishSliceType>()(10 // 使用 immer 中间件11 immer(12 devtools(13 persist(14 (arg) => {15 return {16 createFishSlice(arg),17 createBearSlice(arg),18 };19 },20 {21 name: "store",22 storage: createJSONStorage(() => sessionStorage),23 partialize: (state) => {24 const keys = ["fishes", "bears"];25 const selectedEntries = Object.entries(state).filter((entry) => keys.includes(entry[0]));26 return Object.fromEntries(selectedEntries);27 },28 }29 ),30 {31 name: "app-store",32 }33 )34 )35);3637export default useStore;3、修改@/store/slices/fishSlice.ts,基于 immer 语法改造对应的Action:
xxxxxxxxxx231import { StateCreator } from "zustand";23// 导入全局唯一的store4import useStore from "../index";56const createFishSlice: StateCreator<FishSliceType> = (set) => {7 return {8 // 小鱼干的数量9 fishes: 0,10 };11};1213// 使用useStore的setState方法来修改值14// 小鱼干自增+115export const incrementFishes = () =>16 useStore.setState((state) => {17 state.fishes += 1;18 });1920// 重置小鱼干的数量21export const resetFishes = () => useStore.setState({ fishes: 0 });2223export default createFishSlice;
4、修改@/store/slices/bearSlice.ts,基于 immer 语法改造对应的Action:
xxxxxxxxxx301import { StateCreator } from "zustand";23// 导入全局唯一的store4import useStore from "@/store";56const createBearSlice: StateCreator<BearSliceType> = () => {7 return {8 bears: 0,9 };10};1112export const incrementBears = () =>13 useStore.setState((state) => {14 state.bears += 1;15 });1617export const resetBears = () => useStore.setState({ bears: 0 });1819export const decrementBearsByStep = (step = 1) =>20 useStore.setState((state) => {21 state.bears -= step;22 });2324export const asyncIncrementBears = () => {25 setTimeout(() => {26 incrementBears();27 }, 1000);28};2930export default createBearSlice;效果:

subscribeWithSelector 中间件允许我们订阅 store 中指定数据的变化,配置此中间件的第一步是按需导入它:
xxxxxxxxxx11import { subscribeWithSelector } from "zustand/middleware"第二步是在调用 函数期间,使用此中间件即可:
xxxxxxxxxx401import { create } from "zustand";2import createFishSlice from "@/store/slices/fishSlice";3import createBearSlice from "@/store/slices/bearSlice";4import { immer } from "zustand/middleware/immer";56// 导入 subscribeWithSelector 中间件7import { persist, createJSONStorage, devtools, subscribeWithSelector } from "zustand/middleware";8910const useStore = create<BearSliceType & FishSliceType>()(11 // 使用 subscribeWithSelector 中间件,方便我们订阅 store 中指定数据的变化12 subscribeWithSelector(13 immer(14 devtools(15 persist(16 (arg) => {17 return {18 createFishSlice(arg),19 createBearSlice(arg),20 };21 },22 {23 name: "store",24 storage: createJSONStorage(() => sessionStorage),25 partialize: (state) => {26 const keys = ["fishes", "bears"];27 const selectedEntries = Object.entries(state).filter((entry) => keys.includes(entry[0]));28 return Object.fromEntries(selectedEntries);29 },30 }31 ),32 {33 name: "app-store",34 }35 )36 )37 )38);3940export default useStore;需求:如果每只小熊“至少能获得5条小鱼干”,则 Father 组件的背景色为 lightgreen;否则背景色为lightgray
1、在Father组件中,先基于 selector 选择器获取到小熊的数量:
xxxxxxxxxx21// 获取 bears 的数量2const bears = useStore((state) => state.bears)2、再定义 state 状态,用来保存 Father 组件的背景色:
xxxxxxxxxx21// 背景颜色的 state 状态2const [ bgColor, setBgColor ] = useState<'lightgreen' | 'lightgray'>('lightgray')并为Father组件中最外层的 div 绑定样式对象:
xxxxxxxxxx201import { FC, useState } from 'react'2import useStore from '@/store'34export const Father: FC = () => {5 const bears = useStore(state => state.bears)67 // 背景颜色的 state 状态8 const [bgColor, setBgColor] = useState<'lightgreen' | 'lightgray'>('lightgray')910 return <div style={{ padding: 10, borderRadius: 5, backgroundColor: bgColor }}>11 <h3>Father组件</h3>12 <p>小熊的数量是:{bears}</p>13 <hr />14 <Son1 />15 <hr />16 <Son2 />17 <hr />18 <Son3 />19 </div>20}
3、在 useEffect 中,通过subscribe订阅小鱼干数量的变化,并更新bgColor的值。同时,需要把bears数量设置为useEffect的依赖项,当小熊数量变化时,重新计算bgColor的颜色值:
xxxxxxxxxx341import { FC, useEffect, useState } from 'react'2import useStore from '@/store'34export const Father: FC = () => {5 const bears = useStore(state => state.bears)67 // 背景颜色的 state 状态8 const [bgColor, setBgColor] = useState<'lightgreen' | 'lightgray'>('lightgray')910 useEffect(() => {11 // 订阅 state.fishes 的变化,并更新背景色12 const unSubFn = useStore.subscribe(13 (state) => state.fishes,14 (newValue) => {15 // 判断条件16 setBgColor(newValue > 0 && newValue >= bears * 5 ? 'lightgreen' : 'lightgray')17 },18 { fireImmediately: true })1920 // 清理函数21 return () => unSubFn();22 }, [bears])2324 return <div style={{ padding: 10, borderRadius: 5, backgroundColor: bgColor }}>25 <h3>Father组件</h3>26 <p>小熊的数量是:{bears}</p>27 <hr />28 <Son1 />29 <hr />30 <Son2 />31 <hr />32 <Son3 />33 </div>34}这里为什么小熊的数量要设置为useEffect的依赖项?
因为题目要求是“每只小熊至少...”,要计算的是平均值。实际上监听的是两个值:bears和fishes,任何一个值变化了,平均值都会变化,要实时获取到平均值。
效果:

在zustand中跨slice访问数据的过程很简单,只需要基于全局唯一的 store Hook,调用它的setState或getState即可。
而且,跨slice访问Action方法也很方便,只需要按需导入对应 slice 中的方法进行调用即可。
例如,下面的代码演示了如何同时让bears和fishes的数量自增+1:
1、在@/store/目录下新建tools文件夹,并新建名为commonAction.ts的模块:
xxxxxxxxxx181import useStore from "@/store";2import { incrementBears } from "@/store/slices/bearSlice";3import { incrementFishes } from "@/store/slices/fishSlice";45// 同时让 bears 和 fishes 自增6export const addBearAndFish = () => {7 // 获取 bears 的数量8 const bears = useStore.getState().bears;910 if (bears < 4) {11 // 如果 bears 的数量不足4只,则 bears 和 fishes 同时自增12 incrementBears();13 incrementFishes();14 } else {15 // 否则,只让 fishes 自增16 incrementFishes();17 }18};2、修改@/components/setup.tsx模块中的Son2组件,点击自增bears和fishes按钮,调用刚才定义的Action函数:
xxxxxxxxxx121import { addBearAndFish } from '@/store/tools/commonActions'23const Son2: FC = () => {4 return (5 <>6 <h5>Son2子组件</h5>7 {/* 直接把函数绑定为按钮的点击事件处理函数 */}8 <button onClick={resetBears}>重置bears</button>9 <button onClick={addBearAndFish}>自增bears和fishes</button>10 </>11 )12}效果:

1、在@/store/tools/目录下新建resetters.ts模块,初始化代码如下:
xxxxxxxxxx41const resetters: (() => void)[] = [];23export default resetters;4export const resetAllStore = () => resetters.forEach((resetFn) => resetFn());
2、修改fishSlice.ts中的代码,导入 resetters 数组,并向数组中添加当前 slice 的 reset 函数:
xxxxxxxxxx271import { StateCreator } from "zustand";2import useStore from "../index";34// 1、导入 resetters 数组5import resetters from "../tools/resetters";67// 2、定义初始化数据8const initFishState = {9 fishes: 0,10};1112const createFishSlice: StateCreator<FishSliceType> = (set) => {13 // 3、添加 reset 函数到 resetters 数组中14 resetters.push(() => set(initFishState));15 return {16 initFishState,17 };18};1920export const incrementFishes = () =>21 useStore.setState((state) => {22 state.fishes += 1;23 });2425export const resetFishes = () => useStore.setState({ fishes: 0 });2627export default createFishSlice;
3、修改bearSlice.ts中的代码,导入 resetters 数组,并向数组中添加当前 slice 的 reset 函数:
xxxxxxxxxx381import { StateCreator } from "zustand";2import useStore from "..";34// 1、导入 resetters 数组5import resetters from "../tools/resetters";67// 2、定义初始化数据8const initBearState = {9 bears: 0,10};1112const createBearSlice: StateCreator<BearSliceType> = (set) => {13 // 3、添加 reset 函数到 resetters 数组中14 resetters.push(() => set(initBearState));15 return {16 initBearState,17 };18};1920export const incrementBears = () =>21 useStore.setState((state) => {22 state.bears += 1;23 });2425export const resetBears = () => useStore.setState({ bears: 0 });2627export const decrementBearsByStep = (step = 1) =>28 useStore.setState((state) => {29 state.bears -= step;30 });3132export const asyncIncrementBears = () => {33 setTimeout(() => {34 incrementBears();35 }, 1000);36};3738export default createBearSlice;
4、修改@/components/setup.tsx模块中的Son2组件,为重置Store按钮绑定点击事件处理函数:
xxxxxxxxxx141import { resetAllStore } from '@/store/tools/resetters'23const Son2: FC = () => {4 return (5 <>6 <h5>Son2子组件</h5>7 {/* 直接把函数绑定为按钮的点击事件处理函数 */}8 <button onClick={resetBears}>重置bears</button>9 <button onClick={addBearAndFish}>自增bears和fishes</button>1011 <button onClick={resetAllStore}>重置Store</button>12 </>13 )14}效果:

通过persist中间件提供的clearStorage()函数,就能方便的清除本地存储的数据。示例代码如下:
xxxxxxxxxx191const Son2: FC = () => {2 const clearAll = () => {3 // 重置 store 的数据。这句代码不是重点,可加可不加。4 resetAllStore();5 // 清除本地存储的数据6 useStore.persist.clearStorage();7 }8 9 return (10 <>11 <h5>Son2子组件</h5>12 {/* 直接把函数绑定为按钮的点击事件处理函数 */}13 <button onClick={resetBears}>重置bears</button>14 <button onClick={addBearAndFish}>自增bears和fishes</button>1516 <button onClick={clearAll}>清除本地存储的数据</button>17 </>18 )19}效果:

小结:
使用zustand我有一个体会,有一点和vuex或pinia不同的地方,就是store创建之后,不需要在main.tsx里面引入注册,而是哪里使用,就在哪里引入。其实这只是代码组织的不同而已,vuex也可以做到哪里使用就在哪里引入,只不过在main.js里面引入之后,全局都可以使用了,这样会方便很多。