seo学堂,怎么优化关键词排名优化,wordpress ip验证不当,做网站苏州Zustand 状态管理 安装创建 Store给 Store 添加TS类型约束在页面使用 Store返回 Store 中所有状态在 Store 中使用 async 异步方法使用 Immer Middleware (中间件) 更新深层嵌套的 State使用 get 方法#xff0c;在 set 方法外访问 State 中的数据使用 selector什么是 selecto… Zustand 状态管理 安装创建 Store给 Store 添加TS类型约束在页面使用 Store返回 Store 中所有状态在 Store 中使用 async 异步方法使用 Immer Middleware (中间件) 更新深层嵌套的 State使用 get 方法在 set 方法外访问 State 中的数据使用 selector什么是 selector 为什么要使用 selector 如何自动生成第一层的 selector 使用 shallow 安全返回多个状态 selector使用 devtools 调试工具在生产环境关闭浏览器的状态调试工具给不同的 store 添加别名 使用 persist 浏览器本地保存 State保存到 sessionStoragepartialize 设置本地存储保时只存部分状态排除 Store 中的某些状态清除 store 中的缓存persist 在 middleware 里的顺序 使用 subscribe 订阅关注subscribe 在 Zustand 里是什么意思 为什么要使用 subscribe 使用 subscribe使用 subscribeWithSelectorsubscribeWithSelector 在 middleware 里的顺序 get/setState 在Store外控制 statesetStategetState使用 getState 用于初始化数据 使用分离版本的 Actions简化 StoreTypescript 建议从 store 中抽离 StateCreator 使用 hook 的等方式创建和使用状态管理。可以直接使用 async 异步函数而不需要像 Redux 一样额外安装第三方插件才能实现。
安装
npm install zustand # or yarn add zustand or pnpm add zustand创建 Store
import { create } from zustandexport const useBearStore create((set) ({bears: 0,user: {name: yi,age: 18},increasePopulation: () set((state) ({// ...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),setName: (name: string) set((state) ({...state,user: {...state.user,name}}))
}))zustand 会自动合并第一层的 state,所以第一层可以不使用...state但如果是更深层的状态比如第二层或第三层就还是需要 ...state 修改 state。 给 Store 添加TS类型约束
给 srore 定义类型
import { create } from zustandtype TBearStore {bears: numberuser: {name: stringage: number}increasePopulation: () voidremoveAllBears: () voidsetName: (name: string) void
}export const useBearStore createTBearStore()((set) ({bears: 0,user: {name: yi,age: 18},increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),setName: (name: string) set((state) ({...state,user: {...state.user,name}})),
}))注意给 store 添加 TS 类型时时我们要在传入泛型的后面加一个()。具体原因感兴趣可以查看下面的连接 https://github.com/pmndrs/zustand/blob/main/docs/guides/typescript.md。 在页面使用 Store
Zustand 创建的 Store比较特别 本质上就是一个hook所以它能够很方便的被调用你不需要像 Redux 或者 useContext 一样外面还要包一层传送门。
import { useBearStore } from /stores/bearStore;export const BearBox () {const bears useBearStore((state) state.bears);const increasePopulation useBearStore((state) state.increasePopulation);const removeAllBears useBearStore((state) state.removeAllBears);return (div classNameboxh1Bear Box/h1pbears: {bears}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/button/div/div);
};上面的 useBearStore返回一个 clalback 回调函数在这个回调函数里可以获取到 state这个state 就是 store 里所有的状态然后你可以用它返回任何你在 useBearStore 中定义的 state 和 Action。
返回 Store 中所有状态
在上面使用 store 的示例中我们可以看到我们每次取出 store 中的数据都需要使用 const xx useBearStore((state) state.xx) 方式如果你需要使用很多状态每一个都这么写会很累你可能会想有没有什么方式更简便一点呢
如果是需要返回 store中所有的状态我们可以这么写
import { useBearStore } from /stores/bearStore;export const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();return (div classNameboxh1Bear Box/h1pbears: {bears}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/button/div/div);
};注意如果你不需要全部状态而是为了偷懒而这样写的话可能会引起页面不必要的重复渲染在小的 app 里无关紧要但在大而复杂的项目里就会影响速度。 在 Store 中使用 async 异步方法
import { create } from zustandtype TBearStore {user: {name: stringphone: string}getUserInfo: () Promiseany
}export const useBearStore createTBearStore()((set) ({user: {name: yi,phone: 13246566447},getUserInfo: async () {const res await fetch(https://jsonplaceholder.typicode.com/users/1)const user await res.json()set((state) ({user: {...state.user,name: user.name}}))return user.name}
}))
使用 Immer Middleware (中间件) 更新深层嵌套的 State
import { create } from zustand;
type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;
};export const useCatStore createTCatStoreState()((set, get) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () {set((state) ({cats: {//zustand 只会自动合并第一层的 state, 所以这里要手动合并...state.cats,bigCats: state.cats.bigCats 1,},}))},increaseSmallCats: () {set((state) ({cats: {...state.cats,smallCats: state.cats.smallCats 1,},}))}})
)在页面中使用
import { useCatStore } from /stores/catStore;export const CatBox () {const bigCats useCatStore((state) state.cats.bigCats);const smallCats useCatStore((state) state.cats.smallCats);const increaseBigCats useCatStore((state) state.increaseBigCats);const increaseSmallCats useCatStore((state) state.increaseSmallCats);return (div classNameboxh1Cat Box/h1pbig cats: {bigCats}/ppsmall cats: {smallCats}/pdivbutton onClick{increaseBigCats}add big cats/buttonbutton onClick{increaseSmallCats}add small cats/button/div/div)
}在上面定义的 store 中我们频繁的使用了 ...state 的方式来把 state 先复制一下再定义新的值从而覆盖旧的 state 来更新 store 的状态。 我们可以使用 Immer Middleware 来解决这个问题
首先安装 immer
pnpm i -D immer使用方法也很简单导入 immer 然后在 create 方法中在在包裹一个 immer()
import { create } from zustand;
import { immer } from zustand/middleware/immer;export const useCatStore create(immer((set) ({//...}))
)示例优化上面在 catStore.ts
import { create } from zustand;
import { immer } from zustand/middleware/immer;type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;
};export const useCatStore createTCatStoreState()(immer((set) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () set((state) {state.cats.bigCats;}),increaseSmallCats: () set((state) {state.cats.smallCats;}),}))
)使用 immer 后我们直接通过函数的形式使用 set 方法设置 state 中的值在这个函数里不需要 return (不再需要返回一个对象)。 使用 get 方法在 set 方法外访问 State 中的数据
在使用 zustand 时是无法直接访问 state 中数据的只能通过 zustand 给我们提供的 set 、 get 方法来访问 state状态。所以如果需要再 set 方法外访问 state那我们需要使用 get 方法。
import { create } from zustand;
import { immer } from zustand/middleware/immer;type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;summary: () void;
};export const useCatStore createTCatStoreState()(immer((set, get) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () set((state) { state.cats.bigCats }),increaseSmallCats: () set((state) { state.cats.smallCats }),summary: () {const total get().cats.bigCats get().cats.smallCats;return There are ${total} cats in total. ;}}))
)使用的时候注意summary 是一个函数所以在使用的时候需要调用一下
import { useCatStore } from /stores/catStore;export const CatBox () {const summary useCatStore((state) state.summary);console.log(summary())return (div classNameboxh1Cat Box/h1psmall summary: {summary()}/p/div)
}使用 selector
什么是 selector const bigCats useCatStore((state) state.cats.bigCats) 我们把 useCatStore 括号里的 (state) state.cats.bigCats称为 selector且这个 selector (选择器) 是一个回调函数。 为什么要使用 selector
在上面的实例中我们在页面中消费 store 的时候是非常繁琐的
const bigCats useCatStore((state) state.cats.bigCats);
const smallCats useCatStore((state) state.cats.smallCats);
const increaseBigCats useCatStore((state) state.increaseBigCats);
const increaseSmallCats useCatStore((state) state.increaseSmallCats);
const summary useCatStore((state) state.summary);如果我们要使用所有的状态我们就可以直接从 useCatStore 解构出所有 state 从而简化代码 const {cats: { bigCats, smallCats },increaseBigCats,increaseSmallCats,summary,} useCatStore();我们之前也提到过这种方式 只适用与你需要使用全部状态如果只是使用部分状态那情况就不妙了因为这会导致不必要的重渲染接下来我们来看一个了
假设我们右如下 CatBox、CatBox2 组件, 为了验证页面是否发生重渲染我们给组件添加一个 Math.random()方法
import { useCatStore } from /stores/catStore;export const CatBox () {const {cats: { bigCats, smallCats },increaseBigCats,increaseSmallCats,summary,} useCatStore();console.log(summary());return (div classNameboxh1Cat Box/h1pbig cats: {bigCats}/ppsmall cats: {smallCats}/pp{Math.random()}/pdivbutton onClick{increaseBigCats}add big cats/buttonbutton onClick{increaseSmallCats}add small cats/button/div/div);
};
import { useCatStore } from ../stores/catStore;export const CatBox2 () {const { cats: { bigCats } } useCatStore();return (div classNameboxh1Partial States from catStore/h1pbig cats: {bigCats}/pp{Math.random()}/p/div);
};点击左侧 CatBox 组件 add big cats 按钮时两侧随机数发生变化了这是正常的因为在两个组件中我们都使用了 store 中的 bigCats但是点击 add small cats 按钮时两边又重新产生了随机数说明组件重渲染了。
这就是问什么我们要使用 selector 来调用状态的原因因为他可以避免页面不必要的重复渲染我们更改一下 CatBox 组件中的代码
import { useCatStore } from /stores/catStore;export const CatBox2 () {const bigCats useCatStore((state) state.cats.bigCats);return (div classNameboxh1Partial States from catStore/h1pbig cats: {bigCats}/pp{Math.random()}/p/div);
}; 注意 ⚠️ 上面的代码中useCatStore()括号里放的就是我们上面说的 selector它就是一个回调函数这个回调函数会自动拿到一个 state也就是 useCatStore中的全部 state 状态然后我们按需返回所需要的状态比如我们这里是 bigCats 更改代码后我们回到从新打开页面再次点击 add small cats 按钮时右侧CatBox2 组件的随机数不再发生变化说明使用 selector 方式可以避免页面重渲染。 但是如果页面中需要使用 n 个状态我们这么写还是很拉胯的先别急其实作者在官方文档里给我们提供了一个秘方就是在第一层状态里能大大提高你选择第一层状态时的效率。
如何自动生成第一层的 selector
Auto Generating Selectors
在项目新建 src/utils/createSelectors.ts 文件
import { StoreApi, UseBoundStore } from zustandtype WithSelectorsS S extends { getState: () infer T }? S { use: { [K in keyof T]: () T[K] } }: neverconst createSelectors S extends UseBoundStoreStoreApiobject(_store: S,
) {const store _store as WithSelectorstypeof _storestore.use {}for (const k of Object.keys(store.getState())) {;(store.use as any)[k] () store((s) s[k as keyof typeof s])}return store
}上面这段代码比较简单其实就是把你的 store 作为输入值然后把store 拓展成一个 use 的属性这个 use 属性是一个对象里面包含所有的 state 的 key和它对应的 selector function最后返回 store。 使用 createSelectors 方式也简单
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { createSelectors } from /utils/createSelectors;type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;summary: () void;
};export const useCatStore createSelectors(createTCatStoreState()(immer((set, get) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () set((state) state.cats.bigCats),increaseSmallCats: () set((state) state.cats.smallCats)}))
))在组件中使用
import { shallow } from zustand/shallow;
import { useCatStore } from /stores/useCatStore;export const CatController () {const increaseBigCats useCatStore.use.increaseBigCats() const increaseSmallCats useCatStore.use.increaseSmallCats()return (div classNameboxh1Cat Controller/h1p{Math.random()}/pdivbutton onClick{increaseBigCats}add big cats/buttonbutton onClick{increaseSmallCats}add small cats/button/div/div);
}使用 shallow 安全返回多个状态 selector 如果我们想避免组件重渲染的同时还可以在 store 选择多个状态那就要使用 shallow (平安符)。 // const { increaseBigCats, increaseSmallCats } useCatStore();// const increaseBigCats useCatStore.use.increaseBigCats();// const increaseSmallCats useCatStore.use.increaseSmallCats();import { shallow } from zustand/shallow;const { increaseBigCats, increaseSmallCats } useCatStore((state) ({increaseBigCats: state.increaseBigCats,increaseSmallCats: state.increaseSmallCats,}),shallow);这个 shallow 是一个判断函数它判断第一层状态是否相等需要从 zustand/shallow 中导入。为什么加入 shallow 函数后就可以避免重渲染问题呢因为我们的上面的代码中我们使用 useCatStore 时返回的是一个 object 它每一次都是重新产生的而这个 shallow 函数的作用就是用于比较两个 object 的第一层值是不是一样如果一样就认为相等反之则不相等。如果你的情况更复杂你还可以自己写这个 shallow 比较函数。
我们不仅仅可以返回一个对象还可以返回一个数组
import { shallow } from zustand/shallow;const [increaseBigCats, increaseSmallCats] useCatStore((state) [state.increaseBigCats, state.increaseSmallCats],shallow
);使用 devtools 调试工具 因为 zustand 和 Redux 是同门所以可以直接借用 Redux 的调试工具来调试状态。 Redux DevTools
import { create } from zustand
import { devtools } from zustand/middlewaretype TBearStore {bears: numberincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(devtools((set) ({bears: 0,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),})
))如果触发没效果需要配置一下 Redux tools 将 instance 设置为 Autoselect instances 在生产环境关闭浏览器的状态调试工具
如果你想设置在生产环境下关闭浏览器状态调试可以使用 devtools 的第二个参数这个参数是一个对象我们在对象里加设置 enlabed 属性值为布尔值为 true 时会开启浏览器调试反之则关闭。
import { create } from zustand
import { devtools } from zustand/middlewaretype TBearStore {bears: numberincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(devtools((set) ({bears: 0,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),}),{enabled: true,}
))如果使用了immer, 必须把 devtools 放在 immer 后面因为 immer 可能会改变 state 状态
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { devtools } from zustand/middleware;export const useCatStore create(immer(devtools((set, get) ({// ...})))
)给不同的 store 添加别名
上面有提到我们因为不确定创建的 store 对应的 instance导致调试时看不到状态变化 所以将 instance 设置为 Autoselect instances为了解决这个问题我们在开启devtool时给store设置别名
import { create } from zustand
import { devtools } from zustand/middlewaretype TBearStore {bears: numberincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(devtools((set) ({bears: 0,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),}),{enabled: true,name: Bear Store,}
))如下图设置别名后我们可以通过别名很方便的选择正确的 instance
使用 persist 浏览器本地保存 State 在很多时候我们是需要将状态保存到本地的常规的思路是使用手动将一些数据保存在浏览器的 localStorage 本地缓存中但在 zustand 中提供了更简单的本地存储方法。 Zustand (persisting-store-data)
使用 persist 本地储存状态
import { create } from zustand
import { persist } from zustand/middlewaretype TBearStore {bears: numberincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(persist((set) ({bears: 0,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),}),{// 设置存储的key名称, 且必须是唯一的name: bear Store,}
))保存到 sessionStorage
zustand 默认将开启 persist 的 store 全部保存到浏览器的 localStorage 中。
如果想存储到 sessionStorage
import { create } from zustand
import { persist, createJSONStorage } from zustand/middlewareexport const useBearStore create(persist((set, get) ({bears: 0,addABear: () set({ bears: get().bears 1 }),}),{name: bear Store, // name of the item in the storage (must be unique)storage: createJSONStorage(() sessionStorage), // (optional) by default, localStorage is used},),
)partialize 设置本地存储保时只存部分状态 比如有如下 store 代码
export const useBoundStore create(persist((set, get) ({foo: 0,bar: 1,size: 24,userInfo: {name: yi,age: 25}}),),
)我们只想将 userInfo 保存到本地存储
export const useBoundStore create(persist((set, get) ({foo: 0,bar: 1,size: 24,userInfo: {name: yi,age: 25}}),{name: bound Storepartialize: (state) ({ userInfo: state.userInfo })},),
)partialize 是一个回调函数可以拿到所有 state在这个函数内部需要返回一个对象在这个对象里我们可以仅返回需要本地存储的字段。
排除 Store 中的某些状态
Zustand - partialize
比如我们想排除 foo、size 状态的本地缓存可以这么写
export const useBoundStore create(persist((set, get) ({foo: 0,bar: 1,size: 24,userInfo: {name: yi,age: 25}}),{name: bound Storepartialize: (state) Object.fromEntries(// 根据 key过滤掉相应的 statesObject.entries(state).filter(([key]) ![foo,size].includes(key)),),},),
)清除 store 中的缓存
import { create } from zustand
import { persist } from zustand/middlewaretype TBearStore {bears: numberincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(persist((set) ({bears: 0,increasePopulation: () set((state) ({bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),}),{name: bear Store,}
))import { useBearStore } from /stores/bearStore;export const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();return (div classNameboxh1Bear Box/h1pbears: {bears}/pbutton onClick{increasePopulation}add bear/buttondivbutton onClick{useBearStore.persist.clearStorage}clear storage/button/div/div);
}注意 ClearStorage 方法并不是 RestStorage 上面这种方式可以正确清除浏览器中的 Storage但是并没有清除 memory所以当你再次点击 add bear按钮时 bears的值不会从 0 开始自增而是基于上次的 memory 值来改变的。 如果你要实现 rest states重置状态可以自己在 store 中定义一个重置逻辑
import { create } from zustand
import { persist } from zustand/middlewaretype TBearStore {bears: numbercolor: stringsize: stringincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()(persist((set) ({bears: 0,color: pink,size: big,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 }),reset: () set({bears: 0,color: pink,size: big,})}),{name: bear Store,}
))persist 在 middleware 里的顺序
当同时使用 immer、devtools、persist 时需要把 persist 放在 devtools 中间件里面, 注意 persist 中的第二个参数为必填项必须设置 name 也就是本地存储时的 key 名称。
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { devtools,persist } from zustand/middleware;export const useCatStore create(immer(devtools(persist((set, get) ({// ...}),{name: user Store})))
)关于 persist 更多的用法点击参考链接
使用 subscribe 订阅关注
subscribe 在 Zustand 里是什么意思
我们前面在组件中消费 store 中的数据时都是这么写的 const { increaseBigCats, increaseSmallCats } useCatStore();
const increaseBigCats useCatStore.use.increaseBigCats();
const increaseSmallCats useCatStore.use.increaseSmallCats();// 或者
const [increaseBigCats, increaseSmallCats] useCatStore((state) [state.increaseBigCats, state.increaseSmallCats],shallow
)上面的写法中 selector 返回的状态是 reactive 的与 subscribe 不同的是, reactive 的状态会在每次状态变化后都重渲染。
举个生活中的例子解释 reactive 和 subscribe 的区别比如你是一个 reactive 的小孩那不管你是看到你妈妈在做饭还是做家务或者洗碗你都会放下手上的游戏跑过去看一下妈妈在做什么有什么事是需要帮忙的。 而 subscribe 的小孩不管妈妈在做洗碗、洗衣服、做饭都不会有反应但一旦看到妈妈两眼冒着火花盯着他的时候他就会立刻放下手中的游戏去帮妈妈晾衣服。 总结 subscribe 的小孩只会对某些特定情况做出反应,而 reactive 的小孩则会对所有情况做出反应。
为什么要使用 subscribe
比如熊需要食物主食是鱼所以我们可以创建一个 FoodStore 里面包含一个 fish 状态如果鱼的状态下降到 5 一些我们就将页面背景变为红色大于5的时候变为绿色。这里我们只需要关注 鱼的数量是否大于5 其它的状态我们并不想关注。 按照上面的思路我们会这么定义 Store
import { create } from zustandtype TBearStore {bears: numbercolor: stringsize: stringincreasePopulation: () voidremoveAllBears: () void
}export const useBearStore createTBearStore()((set) ({bears: 0,color: pink,size: big,increasePopulation: () set((state) ({...state,bears: state.bears 1})),removeAllBears: () set({ bears: 0 })})
)type TFishStoreState {fish: number;addOneFish: () void;removeOneFish: () void;removeAllFish: () void;
}export const useFoodStore createTFishStoreState((set) ({fish: 0,addOneFish: () set((state) ({ fish: state.fish 1 })),removeOneFish: () set((state) ({ fish: state.fish - 1 })),removeAllFish: () set({ fish: 0 }),
}));在页面中使用
import { useBearStore, useFoodStore } from /stores/reactiveStore;export const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();const fish useFoodStore((state) state.fish);return (div classNamebox style{{ backgroundColor: fish 5 ? lightgreen : lightpink }}h1Bear Box/h1pbears: {bears}/pp{Math.random()}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/button/div/div);
};export const FoodBox () {const { fish, addOneFish, removeOneFish, removeAllFish } useFoodStore();return (div classNameboxh1Food Box/h1pfish: {fish}/pdivbutton onClick{addOneFish} 1/buttonbutton onClick{removeOneFish} — 1/buttonbutton onClick{removeAllFish}Remove all fish ️ /button/div/div);
};可以看到当我们点击右侧 Food Box 组件的按钮时无论是添加鱼的数量还是减少鱼的数量左侧 Bear Box组件都会重渲染虽然这是正常的因为两个组件我们都用到了fish状态但是如果是更复杂的程序页面频繁的点击操作每次都要重新渲染页面就会影响性能。 使用 subscribe
使用 subscribe 可以订阅全局状态并监听状态变化, 而不需要重渲染。 我们修改一下 BearBox 组件代码
export const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();// const fish useFoodStore((state) state.fish);const [bgColor, setBgColor] useState(lightpink);useEffect(() {// subscribe 返回一个 unsubscribe 函数我们可以通过变量接收const unsub useFoodStore.subscribe((state, prevState) {if (prevState.fish 5 state.fish 5) {setBgColor(lightgreen);} else if (prevState.fish 5 state.fish 5) {setBgColor(lightpink);}})// 返回 unsub即可实现页面销毁的同时也销毁 subscribe 订阅return unsub;}, []);return (div classNamebox style{{ backgroundColor: bgColor }}h1Bear Box/h1pbears: {bears}/pp{Math.random()}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/buttonbutton onClick{useBearStore.persist.clearStorage}clear storage/button/div/div);
};subscribe方法解释
subscribe方法返回一个 listener (监听器) 回调函数可以拿到 state和 prevState 上一次的状态参数。在这个回调函数里写的代码逻辑在 每次state发生变化时都会重新执行但不会引起页面重渲染。subscribe 可以放在组件内也可以放在组件外面。注意如果要放在组件里面时我们要尽量把它放在 useEffect 中subscribe 会返回一个 unSubscribe 的方法我们可以通过一个变量接收然后在 useEffect 中 return即可实现在组件销毁页面隐藏时卸载订阅。
使用 subscribeWithSelector
假如你有很多状态但只关心其中的一部分那我们还可以使用 subscribeWithSelector 中间件来 subscribe 一部分状态。
type TFishStoreState {fish: number;addOneFish: () void;removeOneFish: () void;removeAllFish: () void;
}export const useFoodStore createTFishStoreState()(subscribeWithSelector((set) ({fish: 0,addOneFish: () set((state) ({ fish: state.fish 1 })),removeOneFish: () set((state) ({ fish: state.fish - 1 })),removeAllFish: () set({ fish: 0 }),}))
)在组件中使用
import { useBearStore, useFoodStore } from /stores/subscribeStore;
import { useEffect, useState } from react;
import { shallow } from zustand/shallow;export const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();const [bgColor, setBgColor] useState(lightpink);useEffect(() {const unsub useFoodStore.subscribe((state) state.fish,(fish, prevFish) {if (prevFish 5 fish 5) {setBgColor(lightgreen);} else if (prevFish 5 fish 5) {setBgColor(lightpink);}},{equalityFn: shallow, // 判断两个对象是否相等fireImmediately: true, // 是否在第一次调用初始化时立刻执行});return unsub;}, []);return (div classNamebox style{{ backgroundColor: bgColor }}h1Bear Box/h1pbears: {bears}/pp{Math.random()}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/button/div/div);
};开启 subscribeWithSelector 中间件后store 中的 subscribe 方法和之前就不同的这个 subscribe 会返回三个参数selector、listener、还有一个 options 配置对象。注意 listener 回调函数可以获取的参数也不同拿到的是你选择的 selectedState - selector (state.fish) ,还有 previousState (上一次的 state.fish)。
subscribeWithSelector 在 middleware 里的顺序
subscribeWithSelector 要放在 devtools 和 persist 中间件的中间
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { devtools,persist, subscribeWithSelector } from zustand/middleware;export const useCatStore create(immer(devtools(subscribeWithSelector(persist((set, get) ({// ...}),{name: user Store}))))
)get/setState 在Store外控制 state
使用 getState、setState 方法可以在组件或独立的 JS文件中操作 store 中的状态。
setState 有下面 store 代码
type TFishStoreState {fish: number;addOneFish: () void;removeOneFish: () void;removeAllFish: () void;
}export const useFoodStore createTFishStoreState((set) ({fish: 0,addOneFish: () set((state) ({ fish: state.fish 1 })),removeOneFish: () set((state) ({ fish: state.fish - 1 })),removeAllFish: () set({ fish: 0 }),
}));我们的组件代码如下导出并使用了所有状态但是我还想在不修改 useFoodStore.ts 文件的情况下再添加一个方法比如在页面中增加一个按钮每次点击时让 fish 状态的值 5
import { useFoodStore } from /stores/foodStore;export const FoodBox () {const { fish, addOneFish, removeOneFish, removeAllFish } useFoodStore();return (div classNameboxh1Food Box/h1pfish: {fish}/pdivbutton onClick{addOneFish}add one fish/buttonbutton onClick{removeOneFish}remove one fish/buttonbutton onClick{removeAllFish}remove all fish/button/div/div);
};我们可以使用 setState 方法手动添加一个 Action import { useFoodStore } from /stores/foodStore;export const FoodBox () {const { fish, addOneFish, removeOneFish, removeAllFish } useFoodStore();const add5Fish () {useFoodStore.setState((state) ({fish: state.fish 5,}));};return (div classNameboxh1Food Box/h1pfish: {fish}/pdivbutton onClick{addOneFish}add one fish/buttonbutton onClick{removeOneFish}remove one fish/buttonbutton onClick{removeAllFish}remove all fish/buttonbutton onClick{add5Fish}add 5 fish/button/div/div);
};getState
getState用于在 store 外面获取状态但它是 non-reactive 的什么意思呢
const { fish, addOneFish, removeOneFish, removeAllFish } useFoodStore();const fish useFoodStore((state) state.fish);上面两种消费 store 的方式那就是 reactive 的。 reactive 的状态会在每次状态变化后都重渲染而 subscribe 只会在开启订阅的状态发生变化时重渲染 。
const fish useFoodStore.getState().fish; // non-reactive上面代码我们使用 getState 获取 fish 状态即使当 store 中的 fish 在其它地方发生了改变组件也不会重渲染所以上面的 fish 也不知道store 中的状态发生变化了。
既然页面不更新的话那 getState 能用来干嘛呢
使用 getState 用于初始化数据
在之前的讲到使用 subscribeWithSelector 中我们手动给 bgColor 设置了初始值我们还可以使用 getState 从状态里获取初始值
type TBGColor lightgreen | lightpink | undefinedexport const BearBox () {const { bears, increasePopulation, removeAllBears } useBearStore();const [bgColor, setBgColor] useStateTBGColor(() {return useFoodStore.getState().fish 5 ? lightgreen : lightpink});useEffect(() {const unsub useFoodStore.subscribe((state) state.fish,(fish, prevFish) {if (prevFish 5 fish 5) {setBgColor(lightgreen);} else if (prevFish 5 fish 5) {setBgColor(lightpink);}},{equalityFn: shallow,fireImmediately: true,});return unsub;}, []);return (div classNamebox style{{ backgroundColor: bgColor }}h1Bear Box/h1pbears: {bears}/pp{Math.random()}/pdivbutton onClick{increasePopulation}add bear/buttonbutton onClick{removeAllBears}remove all bears/button/div/div);
};使用分离版本的 Actions简化 Store
Zustand practice-with-no-store-actions
再来回顾一下我们之前定义store 时的写法
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { devtools, subscribeWithSelector, persist } from zustand/middleware;type TFishStoreState {fish: number;addOneFish: () void;removeOneFish: () void;removeAllFish: () void;
}export const useFoodStore createTFishStoreState()(immer(devtools(subscribeWithSelector(persist((set) ({fish: 0,addOneFish: () {set((state) ({ fish: state.fish 1 }))},removeOneFish: () {set((state) ({ fish: state.fish - 1 }))},removeAllFish: () {set({ fish: 0 });},}),{name: food store,})),{name: food store}))
);是不是有点回调地狱的感觉了一层又包含一层 state 和 Action 都在一起而且在页面使用的时候我们还得写各种 const xx useFoodStore(selector) 如果开发中都这么写我想你和我一样肯定会骂人甚至不会考虑这个状态管理库。先别急我们试着把代码重写一下
先剪切所有 Action 方法的代码 修改一下 Action 导出成方法 将报错的 set 替换成 useFoodStore.setState 我们还可以将 state 提取出来最后完成代码如下
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { devtools, subscribeWithSelector, persist } from zustand/middleware;const initialState {fish: 0
}export const useFoodStore createtypeof initialState()(immer(devtools(subscribeWithSelector(persist(() initialState, { name: food store })),{ name: food store }))
);export const addOneFish () {useFoodStore.setState((state) ({ fish: state.fish 1 }))
}export const removeOneFish () {useFoodStore.setState((state) ({ fish: state.fish - 1 }))
}export const removeAllFish () {useFoodStore.setState({ fish: 0 });
}在页面中使用的时候导入对应的 Action 就可以了。 import {useFoodStore,addOneFish,removeOneFish,removeAllFish,
} from /stores/foodStore;export const FoodBox () {const fish useFoodStore((state) state.fish)return (div classNameboxh1Food Box/h1pfish: {fish}/pdivbutton onClick{ addOneFish }add one fish/buttonbutton onClick{ removeOneFish }remove one fish/buttonbutton onClick{ removeAllFish }remove all fish/button/div/div)
}不再需要使用 hook 来调用 store 中的 Action。可以更灵活的分离、组织代码。并且不会存在任何负面效果 指之前提到一系列的重渲染问题
Typescript 建议
从 store 中抽离 StateCreator
来看看下面的代码我们的 store 有很多的状态和 Action我们把所有中间件还有状态都写在了一起非常拥挤
import { create } from zustand;
import { immer } from zustand/middleware/immer;
import { createSelectors } from ../utils/createSelectors;
import { devtools, persist, subscribeWithSelector } from zustand/middleware;type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;summary: () void;
};export const useCatStore createSelectors(createTCatStoreState()(immer(devtools(subscribeWithSelector(persist((set, get) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () set((state) {state.cats.bigCats;}),increaseSmallCats: () set((state) {state.cats.smallCats;}),summary: () {const total get().cats.bigCats get().cats.smallCats;return There are ${total} cats in total. ;},}),{ name: cat store }),),{ name: cat store })))
)我们可以把 StateCreator 从 store 中提取出来
import { type StateCreator, create } from zustand;
import { immer } from zustand/middleware/immer;
import { createSelectors } from /utils/createSelectors;
import { devtools, persist, subscribeWithSelector } from zustand/middleware;type TCatStoreState {cats: {bigCats: number;smallCats: number;};increaseBigCats: () void;increaseSmallCats: () void;summary: () void;
};type TMiddlewares [[zustand/immer, never],[zustand/devtools, unknown],[zustand/subscribeWithSelector, never],[zustand/persist, unknown]
]const createCatSlice: StateCreatorTCatStoreState, TMiddlewares (set, get) ({cats: {bigCats: 0,smallCats: 0,},increaseBigCats: () set((state) {state.cats.bigCats;}),increaseSmallCats: () set((state) {state.cats.smallCats;}),summary: () {const total get().cats.bigCats get().cats.smallCats;return There are ${total} cats in total. ;},
});export const useCatStore createSelectors(createTCatStoreState()(immer(devtools(subscribeWithSelector(persist(createCatSlice, { name: cat store })),{enabled: true,name: cat store,})))
)分享一个快速优化的技巧
使用Ctrl Shift - - - 选中括号内所有StateCreator数据然后右键菜单选择 Refactor (重构)在弹出的菜单选择 extract to constant in enclosing scope 提取到封闭范围中的 constant然后给提取出的 StateCreator 取一个变量名为 createCatSlice给 createCatSlice 定义类型从 zustand 中导入 StateCreator 类型StateCreator 需要传入两个范型第一个是我们定义 Store 的TS类型第二个是中间件的TS类型。
参考链接
Zustand TypeScript 指南