跳到主要内容

学习zustand

上周学习了 zustand 的使用,写一篇博客记录一下,zustand 的存储库 zustand 的存储库

zustand 是一个状态管理的库,它是一个基于 react hooks 的状态管理库,它的特点是简单、轻量、易用,它的 api 设计的很简单,只有三个函数,create、useStore、devtools

安装 zustand

npm i zustand

因为 zustand 是基于 react 的 hooks 的 api,使用还是比较简单易懂的。

我们的存储是一个钩子,可以在里面放任何的东西,原始值,对象,函数,通过create((set)=>({state:0}))的方式来创建一个 store,然后就可以在任何的地方使用这一个钩子,当状态发生改变的时候,就会自动地更新组件。

状态管理的本质

单例模式,我们在在一个地方创建了一个记录,我们在后续的操作中,都是在这个记录上进行操作,这样就可以保证数据的一致性。 观察者模式,当数据发生改变的时候,会通知所有的观察者,这样就可以保证数据的一致性。

那么 zustand 的伪代码就可以写成这样

function CreateStore(initState) {
let state = initState;
const listeners = []; // 存储订阅的监听器

// 更新状态并通知所有订阅者
function setState(newState) {
state = { ...state, ...newState }; // 合并新状态
listeners.forEach((listener) => listener(state)); // 通知所有订阅者
}

// 获取当前的状态
const getState = () => state;

// 订阅状态变化
const subscribe = (listener) => {
listeners.push(listener); // 将监听器添加到订阅列表
// 返回一个取消订阅的函数
return () => {
// 更新 listeners 数组,移除对应的 listener
listeners = listeners.filter((l) => l !== listener);
};
};

// 返回 store 对外的接口
return { setState, getState, subscribe };
}
const store = CreateStore({ count: 0 });

// 订阅状态变化
const unsubscribe = store.subscribe((state) => {
console.log("State updated:", state);
});

// 更新状态
store.setState({ count: 1 }); // 会触发订阅者的回调,打印 "State updated: { count: 1 }"

// 获取当前状态
console.log(store.getState()); // 输出: { count: 1 }

// 取消订阅
unsubscribe();

// 再次更新状态,不会触发取消的订阅者
store.setState({ count: 2 }); // 不会打印任何信息

创建一个 store,初始状态为 { count: 0 },然后通过订阅状态的变化,当状态发生变化时,会通知所有的订阅者,这样就实现了状态管理的功能。

zustand避免了使用 ReactContext 或者传递 props 的操作,在 React 中状态时需要多层组件传递然后通过上下文来实行共享的,而在 zustand 中,任何组件都可以使用 store 中的状态或者更新方法。

zustand 的比较方式,zustand 使用了浅比较来优化性能,当 store 中的某个状态发生变化的时候,订阅了改状态的组件才会被重新渲染。

zustand 的使用

import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";

interface BearState {
bears: number;
increase: () => void;
}
const useBearState = create<BearState>()(
devtools(
persist((set, get) => {
return {
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
};
}),
{
name: "bear-storage",
}
)
);

注意这里的create<BearState>()括号是很重要的,create<BearState>()表示我们在创建 store 的时候指定了 store 的状态,更重要的为了让中间件可以更好的运行

快速上手

type BearType = {
bears: number;
incrementBears: () => void;
resetBears: () => void;
decrementBears: (step?: number) => void;
//异步修改
asyncIncrementBears: () => void;
};
//store/index.ts
import { create } from "zustand";
//首先导入了zustand的create
const useStore = create<BearType>()((set, get) => {
return {
//bears相关的数据
bears: 0,
incrementBears: () => {
set((prevState) => ({ bears: prevState.bears + 1 }));
},
resetBears: () => {
set({ bears: 0 });
},
decrementBears: (step = 1) => {
set((prevState) => ({ bears: prevState.bears - step }));
},
asyncIncrementBears: () => {
setTimeout(() => {
get().incrementBears();
}, 1000);
},
};
});

export default useStore;
import { FC } from "react";
import useStore from "./store";
export const Bear: FC = () => {
const bears = useStore((state) => state.bears);
return (
<div>
<h1>小熊的数量是{bears}</h1>
</div>
);
};
export const Child: FC = () => {
const incrementBears = useStore((state) => state.incrementBears);
const resetBears = useStore((state) => state.resetBears);
const decrementBears = useStore((state) => state.decrementBears);
const asyncIncrementBears = useStore((state) => state.asyncIncrementBears);
return (
<div>
<button onClick={incrementBears}>增加</button>
<button onClick={resetBears}>重置</button>
<button onClick={decrementBears}>减少</button>
<button onClick={asyncIncrementBears}>异步增加</button>
</div>
);
};

使用自动生成选择器

import { StoreApi, UseBoundStore } from "zustand";

type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never;

const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S
) => {
let store = _store as WithSelectors<typeof _store>;
store.use = {};
for (let k of Object.keys(store.getState())) {
(store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
}

return store;
};

然后在 store/index.ts 中使用

import { createSelectors } from "./createSelectors";

import { create } from "zustand";
//首先导入了zustand的create
const useStore = create<BearType>()((set, get) => {
return {
//bears相关的数据
bears: 0,
incrementBears: () => {
set((prevState) => ({ bears: prevState.bears + 1 }));
},
resetBears: () => {
set({ bears: 0 });
},
decrementBears: (step = 1) => {
set((prevState) => ({ bears: prevState.bears - step }));
},
asyncIncrementBears: () => {
setTimeout(() => {
get().incrementBears();
}, 1000);
},
};
});

export default useStore;
export const useStoreWithSelectors = createSelectors(useStore);

然后就可以愉快的使用

import { useStoreWithSelectors } from "./store";
//
const bears11 = useStoreWithSelectors.use.bears(); //可以使用这样的方式来导入获取数据

优化

可以将操作和状态拆分开来,这样可以更好的管理状态,这有利于代码的分割

//1 按需导入create函数
import { create } from "zustand";
//在zustand中中间件本身就是一个函数
import { devtools, persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
//2 创建store的hook
//使用devtools一定要晚于immer
import createSelector from "@/tools/createSelector";

const useBearStore = create<BearStoreType>()(
immer(
devtools(
persist(
() => {
return {
//bears相关的数据
bears: 0,
};
},
{ name: "bear-store" }
),
{ name: "bear-store" }
)
)
);
export const incrementBears = () => {
//使用immer的语法
useBearStore.setState((prevState) => {
prevState.bears += 1;
}); //函数体的大括号不可以省略
};
export const resetBears = () => {
useBearStore.setState({ bears: 0 });
}; //这是对象合并的方式
export const decrementBears = (step = 1) => {
useBearStore.setState((prevState) => {
prevState.bears -= step;
});
};
export const asyncIncrementBear = () => {
setTimeout(() => {
incrementBears();
}, 1000);
};

export default useBearStore;
export const useBearSelector = createSelector(useBearStore);

这里使用了 Zustand 的多个中间件(devtools、persist、immer)来创建一个 store, ,通过封装的操作函数(incrementBears、decrementBears、resetBears 等)来操作 store 的状态。

重置状态的方法,可以实现使用一个 init 状态

//然后再使用useBearStore的时候,可以使用这个initState
import { StateCreator } from "zustand";
import useStore from "@/store";
import resetters from "../tools/resetters";
const initBearState = {
bears: 0,
};
//这里就是我的init状态
const createBearSlice: StateCreator<BearSliceType> = (set) => {
resetters.push(() => {
set(initBearState);
});
return {
...initBearState,
};
};
export const incrementBears = () =>
useStore.setState((prevState) => {
prevState.bears++;
});

export const resetBears = () => {
useStore.setState(initBearState);
};
export const decrementBears = (step = 1) => {
useStore.setState((prevState) => ({ bears: prevState.bears - step }));
};
export const asyncIncrementBears = () => {
setTimeout(() => {
incrementBears();
}, 1000);
};
export default createBearSlice;

使用这种方式,可以使结构更加清晰,在创建的时候就已经使用了 init 状态