Taro微信小程序使用zustand状态管理

4,582 阅读3分钟

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, // 自定义存储事件
    }
  )
)

操作更新状态后如下图所示

image.png

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可以查询状态,但不清晰。

  1. 有个第三方插件 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);
}

image.png

  1. 可以使用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)),
    }),
  ),
)

参考文章