前言
是不是用过 redux 相关状态管理库,其轻松地我们解决跨页面传递信息的问题,但前面出现的很多不是用着很繁琐、臃肿,就是不支持 ts,要不就是文件依赖多体积还大,现在非常流行的一个轻量级状态管理库 zustand 出现了,且目前 star 排名第一,1k 大小左右的体积、基于 hook 的方式、ts的友好支持,加起来深得大家喜爱
如果想了解zustand其内部怎么实现的,其作为一个状态管理库,首先要了解 useSyncExternalStore,其主要用在三方库中,可以脱离 setState 更新状态,这里本篇文章不介绍
ps:有人会觉得 zustand 虽然好用,但是 useContext 也更有性价比呀,个人只能使用感觉体验上不一样,后面使用一下就能感觉到了,小项目简单传递数据 useContext 可能整体更好,zustand 综合使用管理体验更佳,看自己偏好吧
ps:zustand 虽然好用,但尽量别陷入早期 redux 的屎山陷阱中(不要为了使用而使用,更没必要为了学习模仿而使用,模仿demo就足够了),一些公共状态使用就足够了(例如:用户信息管理、企业信息管理等),也能减少耦合度,如果所有状态都使用 zustand 管理,那么恭喜,你的项目强行进入屎山行列,后续自己维护都头疼,更别提别人接手,因此个人也不推荐使用那么多中间件,仅仅个人看法🤣
此外,本文不介绍其原理,只看其使用,如下所示
zustand 使用
安装 zustand
yarn add zustand
基础使用
我们另起一个 store 文件,作为状态管理加工厂,其可以保存状态、设置事件等,使用时直接调用其函数即可返回需要的状态、事件,使用非常方便,就下面操作就能使用了,不用在根部包裹 xml 了
zustand 支持设置下面基本操作
- 支持设置正常 state
- 支持设置 action 事件(plan计划)
- 支持异步 action
- 支持多个 store
设置我们的 store 文件
//设置我们的状态、事件
.store.ts
type State = {
count: number;
//maxCount: number; //多个下面使用也一样,改动哪一个设置哪一个即可
}
type Actions = {
increase: () => void;
decrease: () => void;
reset: () => void;
};
export const useStore = create<State & Actions>((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
reset:() => set({ count: 0 })
setCount: (count) => set({ count })
//fetch: async () => {} //也支持异步函数
}));
ps:如果不使用计划的话,实际上就跟 useContext 差不多了哈,只不过比 useContext 更加便利罢了(至少不用设置模版)😂
ps2:可以使用多个store,也可以使用一个 store
更新、获取状态
我们可以在自己的组件中更新、获取 store 中的状态,使用非常简单,如下所示
componet.tsx
//使用状态和计划
import { useStore } from "./store";
const { count } = useStore(state => state.count)
<div>
<span>{count}</span>
</div>
但是会有个问题存在,就是,当我们使用多个参数的时候,我们不能一次性获取过多的参数,否则会因为用不到的参数的改变而重新渲染当前页面,也就是性能浪费
怎么避免呢?下面列举出一个参数、多个参数、全部参数使用案例情况,能帮助我们更好的使用
使用一个参数
因为只使用一个 state ,像使用 useState 一样使用即可,直接赋值
componet.tsx
//使用状态和计划
import { useStore } from "./store";
//正常取出一个参数,推荐这么使用,不然增加参数的时候就难受了
const { count } = useStore(state => state.count)
//如果 store 中只有一个参数的话,也能这么使用,只是后续需要关注新增state,因此不推荐
// const { count } = useStore()
<div>
<span>{count}</span>
</div>
使用全部参数
使用全部参数,我们直接导出使用即可,无需担心不必要渲染问题,因为无论哪个参数变化,都要监听更新
ps:现在的全部参数,由于版本更新,可能是以后的部分参数,因此还是比较推荐部分参数方案,当然看自己情况灵活采用
componet.tsx
//使用状态和计划
import { useStore } from "./store";
import { useShallow } from "zustand/react/shallow";
//由于这里面全使用到了,直接导出了,实际上全是用,可以使用第一种方案了
const { count, increase, decrease, reset, setCount } = useStore(
useShallow((state) => ({ ...state })) //这里偷个懒,可以一个一个列举,为了变
);
//等同于上面,全部的话,一般这么写,简单,如果不确定全部的话,那么使用上面的一个一个列举开
const { count, increase, decrease, setCount } = useStore();
<div>
<button onClick={decrease}>减少</button>
<span>{count}</span>
<button onClick={increase}>添加</button>
<button onClick={() => setCount(10)}>设置count</button>
</div>
使用部分参数(推荐)
使用部分参数时,由于是多个,我们可以取出来合并,合并的同时使用 useShallow 浅比较,否则因为组合生成新的对象会导致 compare 不一致,也造成不必要渲染
componet.tsx
//使用状态和计划
import { useStore } from "./store";
import { useShallow } from "zustand/react/shallow";
//如果使用了多个,就导出多个,并且使用 useShallow 包裹
const { count, increase } = useStore(
useShallow((state) => ({
count: state.count,
increase: state.increase
}))
);
<div>
<span>{count}</span>
<button onClick={increase}>添加</button>
</div>
懒人版
如果比较懒,个人开发,用的公共数据少,就想像 useContext 一样,直接更新 state,直接用,那么可以这样,也很简单,不用在根部组件包裹 xml 了
.store.ts
type NewState = {
name: string;
age: number;
sex: string;
};
export const useStateStore = create<NewState>(() => ({
name: "谁",
age: 0,
sex: "男",
}));
//更新获取状态
componet.tsx
import { useStateStore } from "./store";
const stateStore = useStateStore();
<span>{stateStore.name}</span>
<button
onClick={() => {
useStateStore.setState({
name: "哈哈",
});
}}
>更新名字</button>
<span>{stateStore.age}</span>
<button
onClick={() => {
useStateStore.setState({
age: 20,
});
}}
>更新年龄</button>
<span>{stateStore.sex}</span>
<button
onClick={() => {
useStateStore.setState({
sex: '女',
});
}}
>更新性别</button>
redux 写法
如果比较偏好过去的 redux 写法,仍然你可以轻微改动一下 store 实现想要的效果
//执行计划
const reducer = (state, { type, by = 1 }) => {
switch (type) {
case types.increase:
return { grumpiness: state.grumpiness + by }
case types.decrease:
return { grumpiness: state.grumpiness - by }
}
}
//store
const useStore = create((set) => ({
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}))
//dispatch 发送改变
const dispatch = useStore((state) => state.dispatch)
//dispatch 一下
dispatch({ type: types.increase, by: 2 })