zustand
背景
小程序之前采用的是官方推荐的dva状态管理,总觉得体量过大,写起来也相对繁琐。
随着hooks的兴起,pmndrs 的三个状态库zustand、jotai、valtio最近比较火热🔥,随即来试用一下。
这里先看下2022年10大最佳React状态库 排行靠前的zustand,
1、特性
- 单向数据流,发布订阅模式
- 拥抱Typescript
- 支持hooks组件使用
- 可以不包裹在全局Context,可以在React之外使用
- 提供中间件middleware扩展
- 模板代码少,中心式状态管理
- 重渲染优化
- 体积小,仅 954B
- 不支持计算属性
2、使用方法
基本功能使用起来还挺方便的, 查询和调度方法都通过useStore一个api
import create from 'zustand'
const useStore = create((set) => {
num : 0,
addNum : () => set(state => ({ num: state.num + 1 }))
})
function Counter(){
const num = useStore(store => store.num)
const addNum = useStore(store => store.addNum)
return (<View>
<View>{ num }</View>
<Button onClick={ addNum }>add</Button>
</View>)
}
re-render
// 这种方式将不会渲染优化
const { num, addNum } = useStore()
// 1. 默认有层浅比较
const num = useStore(state => state.num)
const addNum = useStore(store => store.addNum)
// 2. 解构需要主动浅比较
import shallow from 'zustand/shallow'
const { num, addNum } = useStore(state => ({ num: state.num, addNum: state.addNum }), shallow)
支持异步set方法
addNumAsync: async () => {
await new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 2000)
})
set(state => ({ num: state.num + 1 }))
},
3、模块化
通过slice的方式,合并store
// store/index.ts
import create, { GetState, SetState, StoreApi, StateCreator, State } from 'zustand'
import createUserSlice from './user'
import createCountSlice from './count'
export type StoreSlice<T extends object, E extends object = T> = (
set: SetState<E extends T ? E : E & T>,
get: GetState<E extends T ? E : E & T>
) => T
const useStore = create((set: SetState<any>, get: GetState<any>) => ({
//...createUserSlice(set, get),
...createCountSlice(set, get),
}))
export default useStore
// count.ts
import { StoreSlice } from './index'
export interface INumSlice {
num: number
addNum: (num: number) => void
}
const createCountSlice: StoreSlice<INumSlice> = (set, _get) => ({
num: 0,
addNum: (num: number) =>
set((state) => {
state.num += num
}),
})
export default createCountSlice
4、状态本地化
可集成zustand的中间件进行本地化 Persist middleware Wiki
因为在taro环境下,需要对存取方法做下处理
// ...
import { getStorageSync, setStorageSync, removeStorageSync } from '@tarojs/taro'
// 定义storage操作
const asyncLocalStorage = {
getItem: getStorageSync,
setItem: setStorageSync,
removeItem: removeStorageSync,
}
const useStore = create(
persist(
(set: SetState<any>, get: GetState<any>) => ({
...createUserSlice(set, get),
...createCountSlice(set, get),
}),
{
name: 'local-storage', // 存储key名称
partialize: (state) => ({ username: state.username }), // 指定本地化的状态
getStorage: () => asyncLocalStorage, // 自定义存储事件
}
)
)
操作更新状态后如下图所示
5、immer
对于深层的对象结构更新,需要层层解构,官方也提供了对immer的支持,可以直接更新嵌套对象的属性
需要安装immer依赖 yarn add immer
import produce, { Draft } from 'immer'
// ...
// 参考官方测试用例 https://github.com/pmndrs/zustand/blob/main/tests/middlewareTypes.test.tsx
const immer = <
T extends State,
CustomSetState extends SetState<T>,
CustomGetState extends GetState<T>,
CustomStoreApi extends StoreApi<T>
>(
config: StateCreator<
T,
(partial: ((draft: Draft<T>) => void) | T, replace?: boolean) => void,
CustomGetState,
CustomStoreApi
>
): StateCreator<T, CustomSetState, CustomGetState, CustomStoreApi> => (set, get, api) =>
config(
(partial, replace) => {
const nextState = typeof partial === 'function' ? produce(partial as (state: Draft<T>) => T) : (partial as T)
return set(nextState, replace)
},
get,
api
)
const useStore = create(
persist(
immer((set: SetState<any>, get: GetState<any>) => ({
...createUserSlice(set, get),
...createCountSlice(set, get),
})),
{
name: 'local-storage',
partialize: (state) => ({ username: state.username }),
getStorage: () => asyncLocalStorage,
}
)
)
// count.ts
// ...
const createCountSlice: StoreSlice<INumSlice> = (set, _get) => ({
num: 0,
addNum: () =>
// set((state) => ({ state.num += 1 }))
set((state) => void (++state.num),
})
6、状态调试
官方有Redux devtools的中间件,但是Taro目前还不支持。
Taro目前支持React DevTools,页面hooks可以查询状态,但不清晰。
- 有个第三方插件 simple-zustand-devtools,可以基于React DevTools查看zustand状态,
// ...
import { mountStoreDevtool } from 'simple-zustand-devtools';
export const useStore = create(set => {
// create your zustand store here
});
if (process.env.NODE_ENV === 'development') {
mountStoreDevtool('store', useStore);
}
- 可以使用log中间件来追踪状态变更
// Log every time state is changed
const log = config => (set, get, api) => config(args => {
console.log(" applying", args)
set(args)
console.log(" new state", get())
}, get, api)
const useStore = create(
log(
(set) => ({
bees: false,
setBees: (input) => set((state) => void (state.bees = input)),
}),
),
)