Zustand使用以及在大型应用下的代码组织方式
介绍
zustand是一个精简、快速、可扩展的状态管理器,特别契合react hook下开发。使用上非常简单,在一些特殊场景下也能优雅适配。是我目前使用过之后最喜欢的一个状态管理器
对比redux优势
使用上比redux要简单明了的多,不需要自己去安装各种中间件,不需要包裹在 context provider,同时redux适用的场景zustand都能满足。
也解决了redux dispatch派发action时编辑器无法点击跳转对应文件的问题(手动查找对应文件真的很烦)
缺点:原生不支持class组件,不过可以通过高阶组件包装一层,或者使用组件外
setStategetState,使用context模式也行
使用
简单使用
import './App.css';
import { create } from 'zustand'
const useStore = create((set, get) => ({
count: 1,
inc: () => set(state => ({ count: state.count + 1 })),
}))
function Controls() {
const inc = useStore(state => state.inc)
return <button onClick={inc}>one up</button>
}
function Counter() {
const count = useStore(state => state.count)
return <h1>{count}</h1>
}
function App() {
return (
<div className="App">
<Counter/>
<Controls/>
</div>
);
}
export default App;
使用上就是这么简单,只需要定义一个对象store,不需要区分state还是action,使用上获取状态与更新状态也只需要一个useStoreAPI即可
详细使用
import './App.css';
import {create} from 'zustand'
const useBearStore = create((set, get) => ({
bears: 0,
honey: 0,
// 获取蜂蜜数量
getHoneyCount: () => {
// 在action中使用get读取状态
const bears = get().bears
set({honey: bears})
},
// 杀死n只蜜蜂
// 异步action
killBear: async (number) => {
await new Promise((resolve, reject) => {
setTimeout(() => {
set((state) => ({bears: state.bears - number, honey: state.bears - number}))
}, 2000)
})
},
// set 函数第二个参数默认为 false,即合并值而非覆盖整个 store,可以利用这个特性清空 store,注意包括action也会清除
deleteEverything: () => set({}, true),
// actions 是不变的静态函数,它更新 state 中的值, 但它不是真正的“状态”(state)
actions: {
increasePopulation: () => set((state) => ({bears: state.bears + 1})),
removeAllBears: () => set({bears: 0})
},
}))
export const useBears = () => useBearStore((state) => state.bears)
// 可以只使用一个 hooks 导出所有的 actions,因为内部的action定义完后不会修改,也就不会触发组件的重新渲染
export const useBearActions = () => useBearStore((state) => state.actions)
export const useKillBear = () => useBearStore((state) => state.killBear)
function Counter() {
const bears = useBears()
// const honey = useBearStore((state) => state.honey)
// 一个错误的用法,这里看起来简洁了很多,也能拿到 honey 正常使用并更新state
// 但是这里订阅了整个store的状态,任意状态更新都会引起Counter组件的重新渲染
const {honey} = useBearStore()
return <>
<h1>{bears} around here ...</h1>
<h1>honeys count:{honey} </h1>
</>
}
function Controls() {
const {increasePopulation} = useBearActions()
return <div>
<button onClick={() => {
increasePopulation(2);
}}>one up
</button>
</div>
}
function QueryHoney() {
const getHoneyCount = useBearStore((state) => state.getHoneyCount)
return <div>
<button onClick={getHoneyCount}>query honey</button>
</div>
}
function KillBear() {
const num = 1
const killBear = useKillBear()
return <div>
<button onClick={() => {
killBear(num)
}}>{`kill ${num} bear`}</button>
</div>
}
function App() {
return (
<div className="App">
<Counter/>
<Controls/>
<QueryHoney/>
<KillBear/>
</div>
);
}
export default App;
上述事例包含了 全部状态获取(谨慎使用)、从action中读取“状态(state)”、覆盖状态(set函数有第二个参数,默认为false。它将替换状态而不是合并它们)
上述代码事例为了演示方便写到了一个文件中,实际使用中state与action比较多,可以将state与action hooks这两块代码拆分成两个文件引入使用,根据实际需求自由调整或参考下面的组织方式。
initialState、createStore、自定义hooks式组织方式
该方式比较适合复杂的项目或者状态控制非常多的业务组件,可以清晰明了的组织代码
-
store目录结构
./store ├─createStore.ts ----- // 负责创建 Store 的方法与 Action 方法 ├─index.ts ----------- // 导出所有类型、状态与hooks ├─initialState.ts ---- // 负责 State —— 添加状态类型与初始化状态值 └─useState.ts -------- // 自定义state hooks,方便复用与筛选查询
2.各个文件具体代码
initialState.ts
/*
* 负责 State —— 添加状态类型与初始化状态值
*
* */
// 登录用户信息
export interface User {
userInfo: {
userId: number | undefined;
nickName: string;
userName: string;
portraitUrl?: string;
favoriteCatCategory: number | undefined
}
}
// 用户信息初始值
export const initUser: User = {
userInfo: {
userId: undefined,
nickName: '',
userName: '',
favoriteCatCategory: undefined,
}
}
export interface Token {
accessToken: string | null;
refreshToken: string | null;
}
interface Dog {
dog: {
loading: boolean;
url?: string
}
}
interface Cat {
cat: {
loading: boolean;
url?: string
}
}
// token信息初始值
export const initToken: Token = {
accessToken: null,
refreshToken: null,
}
export const initDog: Dog = {
dog: {
loading: false,
url: ''
}
}
export const initCat: Cat = {
cat: {
loading: false,
url: ''
}
}
export type State = User & Token & Dog & Cat
export const initialState: State = {
...initUser,
...initToken,
...initDog,
...initCat
};
createStore.ts
/*
* 负责创建 Store 的方法与 Action 方法
*
* */
import {create} from 'zustand';
import {persist} from "zustand/middleware";
import type {State} from './initialState';
import {initialState, initUser, initToken} from './initialState';
import {login} from "../services/login";
interface Action {
resetUser: () => void;
resetToken: () => void;
login: (params: { username: string; password: string }) => Promise<boolean>
}
export type Store = State & Action;
// 这里加了持久化中间件persist,数据会储存在localStorage或者sessionStorage(选用session需要配置)里,
// 刷新和关闭网页之后,数据依然能恢复
// 其他比如devtools等中间件类似用法,再次包裹复合使用也可以
export const useStore = create<Store>()(persist(
(set, get) => ({
...initialState,
// 重置用户信息
resetUser: () => {
set({...initUser});
},
// 重置token信息
resetToken: () => {
set({...initToken});
},
login: async (params) => {
const res = await login(params)
console.log('login: ', res)
if (res) {
localStorage.setItem('accessToken', res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken)
set({...res})
return true
} else {
return false
}
},
}), {
name: 'app-storage'
}
))
useState.ts
/*
* 自定义state hooks
* 可以根据参数自定义查询state
* 也可以先查询其他state的数据,根据数据值再查询想要的数据
*
* */
import {useStore} from './createStore';
import {shallow} from "zustand/shallow";
// 提现shallow的作用,可以尝试去掉shallow参数,去Shallow页面点击修改userId和userName的按钮,
// 查看控制台打印值,对比二者的打印值,体会shallow的左右(浅比较在性能优化方面的作用)
//
export const useTokenAndUserName = (type?: string) => useStore((state) => {
if (type && type === 'user') {
return ({
userName: state.userInfo.userName,
accessToken: state.accessToken,
refreshToken: state.refreshToken
})
} else {
return ({
accessToken: state.accessToken,
refreshToken: state.refreshToken
})
}
}, shallow)
export const useAccessToken = () => useStore((state) => state.accessToken)
export const useRefreshToken = () => useStore((state) => state.refreshToken)
export const useUserInfo = () => useStore((state) => state.userInfo)
export const useUserInfoExist = () => {
const accessToken = useAccessToken()
const refreshToken = useRefreshToken()
const userInfo = useUserInfo()
if (accessToken && refreshToken) {
return {
exist: true,
userInfo: userInfo
}
} else {
return {
exist: false
}
}
}
index.ts
export {useStore} from './createStore';
export type {Store} from './createStore';
export type {State} from './initialState';
export * from './useState';
上述事例不止包含了store的组织方式,还包含了使用shallow进行性能优化,组件外对状态进行获取/修改,persist中间件使用
事例代码在分别在utils /request.ts 和store/useState.ts这两个文件中,有代码注释进行说明
切片组织方式
该方式比较适合功能模块独立且非常多的超大型项目,每个功能模块state与action单独抽取维护
- store目录结构
./store
├─cat.ts
├─createStore.ts
├─dog.ts
├─index.ts
├─token.ts
├─useState.ts
└─user.ts
- 关键代码演示
token.ts
import {StateCreator} from 'zustand'
import {login} from "../services/login";
import {Store} from "./createStore";
import {persist} from "zustand/middleware";
export interface Token {
accessToken: string | null;
refreshToken: string | null;
resetToken: () => void;
login: (params: { username: string; password: string }) => Promise<boolean>
}
export const initToken = {
accessToken: null,
refreshToken: null,
}
export const createToken: StateCreator<
Store,
[],
[["zustand/persist", Token]],
Token
> = persist(
(set) => ({
accessToken: null,
refreshToken: null,
// 重置token信息
resetToken: () => {
set({...initToken});
},
login: async (params) => {
const res = await login(params)
console.log('login: ', res)
if (res) {
localStorage.setItem('accessToken', res.accessToken)
localStorage.setItem('refreshToken', res.refreshToken)
set({...res})
return true
} else {
return false
}
},
}), {name: 'zustand-slice'}
)
createStore.ts
import {create} from 'zustand';
import {createUser, User} from "./user";
import {createToken, Token} from "./token";
import {createCat, Cat} from "./cat";
import {createDog, Dog} from "./dog";
export type Store = User & Token & Cat & Dog;
export const useStore = create<Store>()((...a) => ({
...createToken(...a),
...createUser(...a),
...createCat(...a),
...createDog(...a)
}))
zustand组织方式与使用方式很多,几乎能适应react开发中的所有状态场景,这里还有一些中间件没有全部介绍,比如 devtools、immer、subscribeWithSelector、redux、combine等,还可以自己写中间件实现特殊需求
相关资料
这几个相关资料我觉得对学习zustand比较有用,分享给大家