前言
react-redux的reducer,dispatch,mapStateToProps,mapDispatchToProps等概念极大的提高了react的状态管理库的入门门槛。就是使用异步,也得借助redux-thunk。
即使后来官方推荐使用@reduxjs/toolkit,虽然一定程度上降低了心智成本,但一个仓库切片要分别导出actions和reducer,以及使用extraReducers处理异步,也需要记忆大量的API。
zustand则很好的解决了上述API复杂的问题,zustand使用create创建仓库切片,使用set改变数据状态。只要记住这两个API便能解决react的状态管理大部分情况。
zustand使用方式较为接近vue3的状态管理库pinia。想了解pinia的可参考我另一篇文章 pinia(2.0.34) 最新使用指南与持久化缓存
一、特点
-
完整的 ts 支持
-
相比于redux,@reduxjs/toolkit等库,API简单,易于上手
-
基于发布订阅模式实现的响应式
二、安装
yarn add zustand yarn add immer
zustand:状态管理库
immer:以更方便的方式处理不可变状态
三、使用
1. 创建 store
新建store/user.ts
js
import { produce } from 'immer';
import { create } from 'zustand';
interface UserInfo {
name: string;
age: number;
}
interface UserState {
userInfo: UserInfo;
token: string;
updateUserInfo: (parmas: UserInfo) => void;
updateAge: (params: number) => void;
updateToken: (params: string) => void;
}
// 创建状态存储
const useUserStore = create<UserState>((set) => ({
userInfo: {
name: 'zhangsan',
age: 23,
},
token: 'S1',
//更新整个对象
updateUserInfo: (userInfo) => set({ userInfo }), //合并userInfo
//更新对象中某个属性
updateAge: (age) =>
set(
produce((state) => {
state.userInfo.age = age;
}),
),
//更新原始数据类型
updateToken: (token) => set({ token }),
}));
export default useUserStore;
2. 使用 store
新建pages/info.tsx
js
import { useCallback } from 'react';
import useUserStore from '@/store/user';
const Info = () => {
const { userInfo, token, updateUserInfo, updateAge, updateToken } = useUserStore();
const hanlderUser = useCallback(() => {
updateUserInfo({ name: 'lisi', age: 24 });
}, [updateUserInfo]);
const handlerAge = useCallback(() => {
updateAge(userInfo.age + 1);
}, [updateAge, userInfo.age]);
const handlerToken = useCallback(() => {
updateToken('23652');
}, [updateToken]);
return (
<div className="App">
<div>
姓名:{userInfo.name} 年龄:{userInfo.age}
</div>
<div>token:{token}</div>
<button onClick={hanlderUser}>更新用户</button>
<button onClick={handlerAge}>更新年龄</button>
<button onClick={handlerToken}>更新token</button>
</div>
);
};
export default Info;
3. 异步 action
React-redux的异步需要借助redux-thunk中间件,@reduxjs/toolkit也需借助extraReducers选项去处理
zustand可直接通过async,await来处理异步action
新建store/list.ts
js
import { produce } from 'immer';
import { create } from 'zustand';
const getData = () => {
return new Promise<number>((resolve) => {
setTimeout(() => {
resolve(Math.random() * 100);
}, 200);
});
};
interface ListState {
list: number[];
updateList: () => void;
}
const useListStore = create<ListState>((set) => ({
list: [],
updateList: async () => {
try {
const data = await getData();
set(
produce((state) => {
state.list.push(data);
}),
);
} catch {
/* empty */
}
},
}));
export default useListStore;
四、数据持久化
1. 启用持久化
新建store/token.ts
js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface TokenState {
token: string;
updateToken: (params: string) => void;
}
const useTokenStore = create<TokenState>()(
persist(
(set) => ({
token: '',
updateToken: (token) => set({ token }),
}),
{
name: 'token', //存储的名称
},
),
);
export default useTokenStore;
2. 修改存储位置
默认存储到localStorage,可以存储到sessionStorage
js
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface TokenState {
token: string;
updateToken: (params: string) => void;
}
const useTokenStore = create<TokenState>()(
persist(
(set) => ({
token: '',
updateToken: (token) => set({ token }),
}),
{
name: 'token', //存储的名称
storage: createJSONStorage(() => sessionStorage), //存储到sessionStorage
},
),
);
export default useTokenStore;
3. 自定义要持久化的字段
默认会将store中的所有字段都缓存,可以通过partialize指定要缓存的字段
js
import { produce } from 'immer';
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface TokenState {
user: {
name: string;
token: string;
};
updateToken: (params: string) => void;
}
const useTokenStore = create<TokenState>()(
persist(
(set) => ({
user: {
name: '',
token: '',
},
updateToken: (token) =>
set(
produce((state) => {
state.user.token = token;
}),
),
}),
{
name: 'token', //存储的名称
storage: createJSONStorage(() => sessionStorage), //存储到sessionStorage
partialize: (state) => ({ token: state.user.token }), //值存储token字段,而非user
},
),
);
export default useTokenStore;