Zustand 简介与全貌梳理

19 阅读4分钟

Zustand 官方文档写的散乱而残缺,举例又很啰嗦没重点,因此摘选核心内容,辅助理解。

基础

Zustand管理的状态粒度单位称作store(译为存储),就是“状态值 + 操作方法”的组合对象。

React中最简单的使用方式:创建一个 store Hook,在组件中通过 Hook 调用到状态中的某些状态或方法。

  • create 接收一个stateCreatorFn:(set,get,storeApi) => ({...states, ...actions}),返回一个包含所有状态与操作的Hook。
  • set 默认会自动进行浅合并,因此可以只设置修改的某个状态值(直接设置对象或返回新状态对象的函数)。第二个参数为true时,直接替换整个状态对象:set(newState|(state=>newState), true)。当使用immer中间件时,可直接修改,不用返回。
  • 使用Hooks时,需要单条用 selector 函数读取,以便更新时仅触发单个状态的变更。useShallow可返回一个带浅比较的记忆版本 selector 函数。
import { create } from 'zustand';

// 创建 store hook
const useCountStore = create(
    (set, get) => ({
        // 状态值定义:非函数的任意多个值
        title: 'my counter',
        count: 0,
        // 操作方法定义:任意函数,可异步,内部使用set/get使用状态值
        inc: () => set(state => ({count: state.count + 1})),
        update: (countNum) => set({count: countNum}),
        current: () => get().count,
    })
);

// React 中使用
function App() {
    // 使用selector函数,读取 store 中的内容。可用useShallow返回带记忆的selector函数。
    const title = useCountStore(state => state.title);
    const count = useCountStore(state => state.count);
    const inc = useCountStore(state => state.inc);

    return (
        <div>
            <h3>{title}</h3>
            <input readonly value={count} />
            <button onClick={() => inc()}>增加 1</button>
        </div>
    );
}

核心方法

Zustand 的基础是用 createStore 来创建一个纯JS的状态管理 store,而上面示例中使用的 create 则是将纯JS的 store,当做React的外部 store,通过 useSyncExternalStore 订阅,将其包装成了 React Hook

Zustand 还提供了更直接的包装方式: useStore(store, selectorFn) ,原理是一样的。

Zustand 核心的几个方法:

  1. createStore(stateCreatorFn) 创建一个纯JS的状态存储对象。
    • 参数:定义状态与操作的函数 (set, get, store) => {...states, ...actions}
    • 返回store{setState, getState, getInitialState, subscribe}
  2. create(stateCreatorFn) 用来将纯JS状态管理,通过useSyncExternalStore(同步外部状态的Hook)转换为React Hooks
    • 参数同上
    • 返回一个附加了 API 实用程序 setStategetStategetInitialState 和 subscribe 的 React Hook
    • 该 Hook 接收 selector 函数。
  3. useStore(store, selectorFn):可以在React中以Hooks方式直接使用纯JS状态存储。
  4. useShallow(selectorFn):用浅比较记忆化选择器函数。简化了Hooks方式使用store返回只能一条条缓存比较的问题。Zustand 还提供了shallow(a, b),进行浅层比较。
  5. 常用中间件,都返回一个 store 的创建函数
    • combine(initialState, additionalStateCreatorFn) 初始状态+操作,自动推断类型
    • immer(stateCreatorFn) 使用不可变更新方式。搭配方式为:immer(combine(...))
    • persist(stateCreatorFn, options):将状态保存到自定义存储中,默认是localStorage

因为 useSyncExternalStore 同步的外部状态,无法被标记为非阻塞的 Transition 更新,所以 Zustand 状态变化会被立即执行,无法使用 SuspensestartTransition 非阻塞挂起渲染特性。

使用方式

先创建store,然后再使用:

  1. 定义创建函数stateCreatorFn
    1. 状态、操作定义在一个对象中:(set, get) => ({...states, ...actions})
    2. 初始状态单独放置:combine(initStates, (set, get) => ({...actions}))
    3. 附加不可变性:immer(/* 包裹上面两种 */),设置时直接修改set(s => s.count += 1),
    4. 存储状态:persist(/* 可包裹上面三种 */)
  2. 创建store
    1. 原生纯JS:createStore(stateCreatorFn)
    2. Hook: create(stateCreatorFn),创建的Hook方法上包含1中所有方法,可直接调用,但不推荐。
  3. 使用:
    1. 使用Hook:useXxxStore(selectorFn)
    2. 包裹原生JS:useStore(原生store,selectorFn)
    3. 一次选择多个状态值:useShallow(selectorFn)返回记忆版浅比较的 selectorFn

推荐直接创建不可变版本的Hook:create(immer(combine(initStates, actionsFn))),然后使用这个Hook时,需要返回多个状态就使用 useShallow(selectorFn)

各类形式的示例写法:

import { createStore, create, useStore } from 'zustand'
import { combine } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'


// =====1. 定义创建状态函数=====

// 1.1 简单的store创建函数
const xxxCreator = (set,get) => ({
    // 状态定义
    title: 'init title',
    desc: 'init description',
    // action 可为任意方法,可以为异步方法。
    // set 会对状态的第一层进行简单合并。
    updateTitle: (newTitle) => set({title: newTitle}),
    updateDesc: (newDesc) => set(state => ({desc: `updated: ${newDesc}`}))
    getTitle: () => get().title,
});

// 1.2&1.3 使用中间件immer、combine的创建函数 【推荐】
const xxxImmerAndCombineCreator = immer(
    combine(
        {title: 'init title', desc: 'init description'},
        (set, get) => ({
            updateDesc: (newDesc) => set(
                // 使用immer时,set内部,直接对state进行修改,而不是返回新状态
                state => { state.desc = `updated: ${newDesc}` }
            ),
            // ...
        })
    )
);


// =====2. 创建store=====

// 2.1 定义一个纯JS的原始store
const xxxStore = createStore(xxxCreator);

// 2.2 定义一个React Hook的store。useXxxStore上包含所有xxxStore上的方法。
const useXxxStore = create(xxxCreator);


// =====3. 组件中使用store=====
   
// 在React组件中使用:
function App() { 
    // 3.1 直接使用定义好的store Hook
    const xxxStoreTitle = useXxxStore(state => state.title);
    // 3.2 包裹原生js store使用
    const xxxStoreDesc  = useStore(xxxStore, state => state.desc);

    // 3.3 用useShallow创建记忆化的selectorFn,返回多个状态,进行浅层比较【推荐】
    const {title, desc} = useXxxStore(
        useShallow(
            state => ({title: state.title, desc: state.desc})
        )
    );

    return (
        <div>
            <h2>{title}</h2>
            <p>{desc}</p>
        </div>
    );
}