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 核心的几个方法:
createStore(stateCreatorFn)创建一个纯JS的状态存储对象。- 参数:定义状态与操作的函数
(set, get, store) => {...states, ...actions} - 返回
store:{setState, getState, getInitialState, subscribe}
- 参数:定义状态与操作的函数
create(stateCreatorFn)用来将纯JS状态管理,通过useSyncExternalStore(同步外部状态的Hook)转换为React Hooks。- 参数同上
- 返回一个附加了 API 实用程序
setState、getState、getInitialState和subscribe的 React Hook - 该 Hook 接收 selector 函数。
useStore(store, selectorFn):可以在React中以Hooks方式直接使用纯JS状态存储。useShallow(selectorFn):用浅比较记忆化选择器函数。简化了Hooks方式使用store返回只能一条条缓存比较的问题。Zustand 还提供了shallow(a, b),进行浅层比较。- 常用中间件,都返回一个 store 的创建函数
combine(initialState, additionalStateCreatorFn)初始状态+操作,自动推断类型immer(stateCreatorFn)使用不可变更新方式。搭配方式为:immer(combine(...))persist(stateCreatorFn, options):将状态保存到自定义存储中,默认是localStorage
因为 useSyncExternalStore 同步的外部状态,无法被标记为非阻塞的 Transition 更新,所以 Zustand 状态变化会被立即执行,无法使用 Suspense、startTransition 非阻塞挂起渲染特性。
使用方式
先创建store,然后再使用:
- 定义创建函数
stateCreatorFn:- 状态、操作定义在一个对象中:
(set, get) => ({...states, ...actions}) - 初始状态单独放置:
combine(initStates, (set, get) => ({...actions})) - 附加不可变性:
immer(/* 包裹上面两种 */),设置时直接修改set(s => s.count += 1), - 存储状态:
persist(/* 可包裹上面三种 */)
- 状态、操作定义在一个对象中:
- 创建
store:- 原生纯JS:
createStore(stateCreatorFn) - Hook:
create(stateCreatorFn),创建的Hook方法上包含1中所有方法,可直接调用,但不推荐。
- 原生纯JS:
- 使用:
- 使用Hook:
useXxxStore(selectorFn) - 包裹原生JS:
useStore(原生store,selectorFn) - 一次选择多个状态值:
useShallow(selectorFn)返回记忆版浅比较的 selectorFn
- 使用Hook:
推荐直接创建不可变版本的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>
);
}