状态库zustand的高级用法和特性

935 阅读3分钟

zustand是目前比较流行的用于替代redux库的状态管理方案,主要有以下几个优点 更少的样板代码,基于状态变更的渲染,基于hooks的使用方法,集中的状态管理等。

基本使用:

import { create } from 'zustand'
const useCountStore = create((set) => ({  
    count: 0,  
    inc: () => set((state) => ({ count: state.count + 1 })),  
}))

这样就可以在组件中使用useCountStore了

import { shallow } from 'zustand/shallow'
import { useShallow } from 'zustand/react/shallow';

// shallow 避免不必要的重渲染
zustand v4版本写法 
const [count, inc ] = useCountStore(state=>[count,inc], shallow)
or
const {count, inc} = useCountStore(state=>{count,inc}, shalow)

或者使用 zustand v5版本写法 
const { count, inc } = useCountStore(useShallow(state => ({ count: state.count,inc:state.inc })) );

纯js操作环境下取值:
const state = useCountStore.getState()
state.值名称

解构成数组或者对象使用更方便。

actions操作除了可以定义在create方法中, 还可以定义在外部使用 eg:

export const useBoundStore = create(() => ({
  count: 0,
  text: 'hello',
}))

export const inc = () =>
  useBoundStore.setState((state) => ({ count: state.count + 1 }))

export const setText = (text) => useBoundStore.setState({ text })

通过setState方法可以操作store里面的数据。

嵌套对象: 使用immer

首先需要安装immer

npm install immer

immer 通过producers操作原始数据,达到数据不可变的目的,对于深层数据的操作具有便捷性和易用性等特征。同时简化了不可变数据结构的处理。

嵌套数据结构eg:

import {produce} from "immer"

// 复杂数据结构例子
const store = {
    users: new Map([
        [
            "17",
            {
                name: "Michel",
                todos: [
                    {
                        title: "Get coffee",
                        done: false
                    }
                ]
            }
        ]
    ])
}

// 深度更新
const nextStore = produce(store, draft => {
    draft.users.get("17").todos[0].done = true
})

// 过滤
const nextStore = produce(store, draft => {
    const user = draft.users.get("17")

    user.todos = user.todos.filter(todo => todo.done)
})

nextStore是一份新的经过处理过的对象,与原始的对象不同。draft是 根据 store 生成的草稿状态,它是 store 的代理,对 draft 所做的任何修改都将被记录并用于生成 nextStore 。在此过程中,store (即原始状态)将不受影响。

切片模式:

定义两份或者多份数据存储,并最终合并成一份store使用。 多个store存在时十分有用。

样板代码:

import { create } from 'zustand'

export const createFishSlice = (set) => ({
    fishes: 0,
    addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

export const createBearSlice = (set) => ({
    bears: 0,
    addBear: () => set((state) => ({ bears: state.bears + 1 })),
    eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
// 一个函数中更新多份store数据
export const createBearFishSlice = (set, get) => ({
    addBearAndFish: () => {
        get().addBear()
        get().addFish()
    },
})

export const useBoundStore = create((...a) => ({
    ...createBearSlice(...a),
    ...createFishSlice(...a),
    ...createBearFishSlice(...a), // 更新多份store数据操作。
}))

useShallow预防出现重渲染。取值时使用useShallow包裹取值状态

const state = useEGStore(useShallow((state) => Object.keys(state)))
//对象取值
const { nuts, honey } = useBearStore(
  useShallow((state) => ({ nuts: state.nuts, honey: state.honey })),
)
//数组取值
const [nuts, honey] = useBearStore(
  useShallow((state) => [state.nuts, state.honey]),
)

zustand的订阅和取消订阅

实际过程中会根据状态来进行具体的业务开发,订阅操作是十分强大的功能。适用于跨组件数据共享、数据监听操作。

import { subscribeWithSelector } from 'zustand/middleware'

需要使用subscribeWithSelector中间件包裹状态 否则不能细粒度的监听状态变化。

import { shallow } from 'zustand/shallow'
const useDogStore = create(
  subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })),
)

//  paw改变时执行回调
const unsub2 = useDogStore.subscribe((state) => state.paw, console.log)
// 新旧值监听
const unsub3 = useDogStore.subscribe(
  (state) => state.paw,
  (paw, previousPaw) => console.log(paw, previousPaw),
)
// Subscribe also supports an optional equality function
// 可选 浅比较options
const unsub4 = useDogStore.subscribe(
  (state) => [state.paw, state.fur],
  console.log,
  { 
      equalityFn: shallow, // 浅比较
      fireImmediately: true, // 是否立即执行监听
  },
)
// 返回取消监听函数 可在组件销毁时执行。

持久化操作

使用persist持久化中间件 将数据存储在Storge里

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

const useFishStore = create(
  persist(
    (set, get) => ({
      fishes: 0,
      addAFish: () => set({ fishes: get().fishes + 1 }),
    }),
    {
      name: 'food-storage', // name of the item in the storage (must be unique)
      storage: createJSONStorage(() => sessionStorage), // 默认 sessionStorage 'localStorage' is used 
    },
  ),
)