Zustand
Zustand 是基于 Flux 思想, 也就是类似 redux 单向数据流模型来实现的 小型、快速和可扩展的状态管理, 它有基于 hooks 舒适的 API, 非常灵活便捷。
Flux 单向数据流


看到上图是不是很眼熟, 就是 redux 的基本原理了
默认版本是 react 版本, vue 的版本是 - Zustand-Vue, 另外还有一个 Zustand-Pub 是为 Iframe、微前端、Module Fedetation、模块化、组件化 等业务场景, 提供 跨应用、跨框架 的 状态管理 及 状态共享 能力。
优缺点
优点
- 轻量, 使用简单, 上手门槛低
- 不依赖
react上下文, 可在组件外调用 - 支持存储全局状态
- 通用的状态解决方案
- 支持多个 store
- TS 支持较好
缺点
框架本身不支持 computed 属性, 但可基于 middleware 机制通过少量代码间接实现 computed , 或基于第三方库 zustand-computed 实现
安装
npm install zustand
# or
yarn add zustand
# or
pnpm add zustand
使用
创建 store
初始化
import { create } from "zustand";
// create():存在三个参数, 第一个参数为函数, 第二个参数为布尔值
// 第一个参数:(set、get、api)=>{…}
// 第二个参数:true/false
// 若第二个参数不传或者传false时, 则调用修改状态的方法后得到的新状态将会和create方法原来的返回值进行融合;
// 若第二个参数传true时, 则调用修改状态的方法后得到的新状态将会直接覆盖create方法原来的返回值。
export const useCountStore = create((set) => ({
count: 0,
time: 1234,
setCount: (num: number) => set({ count: num }),
inc: () => set((state) => ({ count: state.count + 1 })),
minus: () => set((state) => ({ count: state.count - 1 })),
}));
// 计数器 Demo 快速上手
import React from "react";
import { useCountStore } from "./store/count";
export default function Demo() {
// 在这里引入所需状态
const { count, setCount, inc, minus } = useCountStore();
return (
<div>
{count}
<input
onChange={(event) => {
setCount(Number(event.target.value));
}}
/>
<button onClick={inc}>增加</button>
<button onClick={minus}>减少</button>
</div>
);
}
Selector
const state = useCountStore();
上面的例子中, 在使用状态时, 只要每次状态更新, 组件都会重新进行更新, 这就导致, 如果状态很多的话, 更新太过频繁。
如何动态更新, 而不是每次状态改变就更新呢?
上面的方法同样可以这么写:
// 获取状态
const count = useCountStore((state) => state.count);
// 获取放发, 然后使用
const setCount = useCountStore((state) => state.setCount);
return (
<div>
{count}
<input
onChange={(event) => {
setCount(Number(event.target.value));
}}
/>
</div>
);
那么如果一次性获取多个状态的场景, 应该怎么处理?
import { shallow } from "zustand/shallow";
// 选择 Object, 当`state.count`或`state.time`发生变化后, 组件重新渲染
const { count, time } = useCountStore((state) => ({ count: state.count, time: state.time }), shallow);
// 选择 Array, 当`state.count`或`state.time`发现变化后, 组件重新渲染
const [count, time] = useCountStore((state) => [state.count, state.time], shallow);
// 选择 Map, 当`state.treats`的排序、数量和 key 发生变化后, 组件重新渲染
const treats = useCountStore((state) => Object.keys(state.treats), shallow);
当然也可以自己根据使用场景需求, 来重写对比函数
const treats = useBearStore(
(state) => state.treats,
(oldTreats, newTreats) => compare(oldTreats, newTreats)
);
获取状态/设置状态
// 获取
const useFishStore = create((set, get) => ({
state1: {},
getState: () => {
return get().state1;
},
}));
// 设置
const useFishStore = create((set) => ({
state1: {},
getState: (newState) => {
set((state) => {
return { ...state, state1: newState };
});
},
}));
层级较深的状态修改
// 常规操作
normalInc: () =>
set((state) => ({
deep: {
...state.deep,
nested: {
...state.deep.nested,
obj: {
...state.deep.nested.obj,
count: state.deep.nested.obj.count + 1,
},
},
},
}));
其他方式, 官方推荐了三个, 这里不多赘述, 根本就是针对深层数据的修改, 可以根据个人喜好使用
外部访问 订阅
有时我们需要以非响应式的方式访问状态, 或者对存储进行操作。对于这些情况, 生成的 hook 具有附加在其原型上的实用函数。
const useDogStore = create(() => ({ paw: true, snout: true, fur: true }))
// 获取非反应性最新状态
const paw = useDogStore.getState().paw
// 监听所有更改, 对每个更改同步触发
const unsub1 = useDogStore.subscribe(console.log)
// 更新状态, 将触发监听器
useDogStore.setState({ paw: false })
// 取消监听
unsub1()
// 当然, 你可以像往常一样使用钩子
const Component = () => {
const paw = useDogStore((state) => state.paw)
...
订阅 selector 监听变化
你可以用到 subscribeWithSelector 中间件。
使用这个中间件, subscribe 新增了一些额外的功能。
// subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
import { subscribeWithSelector } from "zustand/middleware";
const useDogStore = create(subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })));
// 仅限 `paw` 修改时, 触发监听
const unsub2 = useDogStore.subscribe((state) => state.paw, console.log);
// subscribe 还可以监听到旧值
const unsub3 = useDogStore.subscribe(
(state) => state.paw,
(paw, previousPaw) => console.log(paw, previousPaw)
);
// subscribe 也支持自定义相等函数
const unsub4 = useDogStore.subscribe((state) => [state.paw, state.fur], console.log, { equalityFn: shallow });
// 立即订阅并触发
const unsub5 = useDogStore.subscribe((state) => state.paw, console.log, {
fireImmediately: true,
});
异步
异步获取数据, 修改状态, 可以直接使用 async/await
const useFishStore = create((set) => ({
fishies: {},
fetch: async (pond) => {
const response = await fetch(pond);
set({ fishies: await response.json() });
},
}));
瞬时更新
瞬时更新经常发生在状态变化时
subscribe 函数允许组件绑定到状态部分, 而不必强制在更改时重新呈现。最好与 useEffect 结合使用, 以便在卸载时自动取消订阅。当允许您直接更改视图时, 这种方式将极大地优化你的应用性能。
const useScratchStore = create(set => ({ scratches: 0, ... }))
const Component = () => {
// 获取初始状态
const scratchRef = useRef(useScratchStore.getState().scratches)
// 挂载时连接到 store , 卸载时断开连接, 捕获引用中的状态变化
useEffect(() => useScratchStore.subscribe(
state => (scratchRef.current = state.scratches)
), [])
...