zustand轻量级状态管理库

285 阅读5分钟

前言

是不是用过 redux 相关状态管理库,其轻松地我们解决跨页面传递信息的问题,但前面出现的很多不是用着很繁琐、臃肿,就是不支持 ts,要不就是文件依赖多体积还大,现在非常流行的一个轻量级状态管理库 zustand 出现了,且目前 star 排名第一,1k 大小左右的体积、基于 hook 的方式、ts的友好支持,加起来深得大家喜爱

如果想了解zustand其内部怎么实现的,其作为一个状态管理库,首先要了解 useSyncExternalStore,其主要用在三方库中,可以脱离 setState 更新状态,这里本篇文章不介绍

zustand 参考地址

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 })