Zustand Store 对象底层实现
在深入研究 store
对象的具体实现之前,我们首先需要聚焦于其核心的 TypeScript 类型系统 。这套类型系统是理解 zustand
设计哲学和功能实现的关键。
一、类型系统:StoreApi
与 SetStateInternal
StoreApi<T>
:Store 的核心接口
store
对象(在源码中常被称为 api
)的类型是通过 StoreApi<T>
接口定义的。这个接口是整个 zustand
状态管理的核心,它规定了 store
对象必须具备的四个基本方法。
typescript
export interface StoreApi<T> {
setState: SetStateInternal<T>;
getState: () => T;
getInitialState: () => T;
subscribe: (listener: (state: T, prevState: T) => void) => () => void;
}
- 泛型参数
T
:代表了我们应用中状态对象的具体类型,也就是creator
函数返回的那个对象的类型。大多数情况下,TypeScript 的类型推断系统能自动识别这个类型。但当状态结构复杂时,建议手动指定以确保类型安全。 getState()
和getInitialState()
:这两个函数相对直观,分别用于获取当前状态和初始状态,返回值都是状态类型T
。subscribe()
:这是一个典型的发布-订阅模式实现。它接受一个订阅者(listener) 函数作为参数,并返回一个用于 取消订阅的函数。setState
:这是最核心的状态更新函数,其类型定义SetStateInternal<T>
相对复杂,我们接下来详细解析。
SetStateInternal<T>
:函数重载的巧妙设计
setState
的类型定义利用了 TypeScript 的高级特性,以支持多种调用方式,同时保证类型安全。
css
type SetStateInternal<T> = {
_(partial: T | Partial<T> | { _(state: T): T | Partial<T> }['_'], replace?: false): void;
_(state: T | { _(state: T): T }['_'], replace: true): void;
}['_'];
初看之下,这个语法可能令人困惑。它实际上是函数重载 的一种高级定义方式。通过定义一个对象类型,并立即取出其 _
属性的类型,我们得到了一个合并后的函数签名。
为什么不直接使用函数重载?
你可能会问,为什么不直接写成这样:
这种写法在大多数情况下是可行的,但当函数参数的类型依赖于函数自身的返回值类型时,就会遇到循环引用的问题。例如:
在 UpdateFn
的定义尚未完成时,ReturnType<UpdateFn>
是未知的,TypeScript 无法解析,因此会报错。为了解决这个问题,我们可以利用对象属性的延迟解析特性:
TypeScript 允许在对象属性的定义中引用同一对象的其他属性,只要不构成死循环即可。这为我们解决循环引用问题提供了优雅的方案。
typescript
// 传统的函数重载定义
type Add = {
(a: number, b: number): number; // 重载1:两个数字相加
(a: string, b: string): string; // 重载2:两个字符串拼接
};
typescript
type UpdateFn = (
updater: State | ((prev: State) => State),
callback?: (newState: ReturnType<UpdateFn>) => void // ❌ 错误:循环引用
) => State;
typescript
type UpdateFnWrapper = {
_: (
updater: State | ((prev: State) => State),
// 回调参数类型引用了对象属性 `_` 的返回值,而非直接引用 UpdateFn
callback?: (newState: ReturnType<UpdateFnWrapper['_']>) => void
) => State;
};
回到 SetStateInternal
,它的两个重载分别对应了两种状态更新模式:
- 合并更新 (
replace?: false
) :这是默认的更新方式。
-
partial
参数可以是完整的状态对象T
。- 也可以是部分状态对象
Partial<T>
。 - 还可以是一个函数式更新 ,接受前一个状态
state
并返回新的状态。
- 替换更新 (
replace: true
) :当replace
标志为true
时,整个状态对象将被完全替换,因此partial
参数必须是完整的状态对象T
。
通过这种精巧的类型设计,zustand
的 setState
函数在提供灵活性的同时,也保证了严格的类型安全。
工具类型
在类型系统中,zustand
还定义了一些辅助工具类型,以便在后续的类型推导和操作中使用。
typescript
// 用于从 store 或 api 中推断出 state 的类型
export type ExtractState<S> = S extends { getState: () => infer T } ? T : never;
// 安全地获取对象属性类型,如果不存在则返回备用类型 F
type Get<T, K, F> = K extends keyof T ? T[K] : F;
二、中间件与类型扩展
zustand
的一大核心特性是其强大的**中间件(Middleware)**机制。中间件能够拦截并增强 set
、get
和 api
对象,从而为 store
附加新功能(如持久化、日志、immer
集成等)。这种增强在 TypeScript 层面需要一套复杂的类型操作来实现。
StoreMutators
:中间件的类型扩展接口
中间件可能会给 store
的 api
对象增加新的属性或方法。为了在类型层面反映这些变化,zustand
引入了 StoreMutators
接口。有趣的是,它在源码中最初是一个空接口:
csharp
export interface StoreMutators<S, A> {}
这是一个巧妙的设计,利用了 TypeScript interface
的**声明合并(Declaration Merging)**特性。开发者(或中间件库)可以向这个接口中不断添加新的类型定义,从而"注册"自己的中间件类型。
swift
// 开发者可以像这样扩展中间件类型
export interface StoreMutators<S, A> {
devtools: Write<Cast<S, object>, { devtools: A }>;
persist: Write<S, { persist: A }>;
immer: Write<Cast<S, object>, object>;
subscribeWithSelector: Write<S, { subscribe: A }>;
// 可以扩展更多中间件
}
这个接口就像一个类型层面的 switch...case
语句。当你需要为一个 store
类型 S
应用一个中间件时,你提供中间件的标识符(如 persist
)和它所附加的类型 A
,然后从 StoreMutators
中取出对应的转换后类型。
这里用到了两个关键的工具类型:
Cast<T, U>
:T extends U ? T : U
,确保类型T
兼容类型U
。Write<T, U>
:Omit<T, keyof U> & U
,用U
中的属性覆盖T
中的同名属性,实现类型的"写入"或"更新"。
Mutate<S, Ms>
:递归处理中间件类型列表
我们通常会使用多个中间件,zustand
需要一个方法来按顺序将中间件类型依次应用到 store
类型上。这就是 Mutate<S, Ms>
的作用。
typescript
export type Mutate<S, Ms> = number extends Ms['length' & keyof Ms]
? S
: Ms extends []
? S
: Ms extends [[infer Mi, infer Ma], ...infer Mrs]
? Mutate<StoreMutators<S, Ma>[Mi & StoreMutatorIdentifier], Mrs>
: never;
这个类型的工作逻辑类似于 JavaScript 中的数组 reduce
操作:
- 边界检查:
-
- 如果
Ms
(中间件列表)是一个长度不确定的数组,直接返回原始store
类型S
。 - 如果
Ms
是一个空数组[]
,说明所有中间件都已处理完毕,返回当前的store
类型S
。
- 如果
- 递归处理:
-
- 使用
infer
关键字从Ms
数组中推断出第一个中间件的标识符Mi
和附加类型Ma
,以及剩余的中间件列表Mrs
。 - 最关键的一步:
StoreMutators<S, Ma>[Mi & StoreMutatorIdentifier]
。这里从StoreMutators
中取出经过当前中间件Mi
处理后的新store
类型。 - 将这个新的
store
类型 和剩余的中间件列表Mrs
再次传入Mutate
,进行递归处理。
- 使用
- 结束条件 :当最后一个中间件处理完毕后,
Mrs
会变成空数组,递归进入第二个分支,返回最终被所有中间件增强过的store
类型。
StateCreator<T, Mis, Mos>
:状态创建函数的类型
StateCreator
是定义我们状态和业务逻辑的核心函数。它的类型定义也与中间件紧密相关。
typescript
export type StateCreator<
T,
Mis extends [StoreMutatorIdentifier, unknown][] = [],
Mos extends [StoreMutatorIdentifier, unknown][] = [],
U = T,
> = ((
setState: Get<Mutate<StoreApi<T>, Mis>, 'setState', never>,
getState: Get<Mutate<StoreApi<T>, Mis>, 'getState', never>,
store: Mutate<StoreApi<T>, Mis>,
) => U) & { $$storeMutators?: Mos };
zustand
将中间件分为两类:
Mis
(Inbound Middlewares) :对creator
函数的参数 (set
,get
,store
)进行修改的中间件。Mos
(Outbound Middlewares) :对creator
函数的返回值(即最终的状态对象)进行修改的中间件。
注意最后一行 & { $$storeMutators?: Mos }
。这行代码巧妙地将 Mos
(出站中间件)的信息附加到了函数对象自身的一个特殊属性 $$storeMutators
上。
当 zustand
创建最终状态时,它会检查这个 $$storeMutators
属性。如果发现有记录,就会调用相应的逻辑来处理 creator
函数返回的原始状态,并返回处理后的结果。整个过程是类型驱动 的:TypeScript 确保修改器的类型正确,而 zustand
的运行时逻辑则确保这些修改被正确执行。
三、Store 的创建:createStore
与 createStoreImpl
store
对象的创建是通过 createStore
函数完成的。这个函数在内部实际调用了 createStoreImpl
。让我们先从它们的类型定义开始。
createStore
:灵活的柯里化设计
createStore
函数的设计非常灵活,它既可以接受一个 creator
函数直接创建 store
,也可以不传递参数,返回一个"待创建"的函数(实际上就是 createStoreImpl
)。这种设计通过函数重载实现。
ini
type CreateStore = {
// 重载 1: 接受 creator 函数,直接创建 store
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
): Mutate<StoreApi<T>, Mos>;
// 重载 2: 不接受参数,返回一个可以创建 store 的函数(柯里化)
<T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
) => Mutate<StoreApi<T>, Mos>;
};
函数的实际实现巧妙地利用了 JavaScript 的特性来匹配这个类型定义:
javascript
export const createStore = ((createState) =>
createState ? createStoreImpl(createState) : createStoreImpl) as CreateStore;
关于调用签名中的泛型 <T>
你可能会注意到,在类型定义中存在泛型 <T>
,但在实际实现中却没有。这是 TypeScript 的一个高级特性。这里的 <T>
是函数调用签名 的一部分,而不是类型别名 CreateStore
本身的一部分。
CreateStore
类型描述了一个"契约":任何被标记为此类型的函数,在被调用时,其泛型 <T>
的具体类型将由 TypeScript 根据传入的 initializer
参数自动推断出来。
ini
type CreateArray = {
// 这里的 <T> 是函数"调用签名"的一部分
<T>(input: T): T[];
};
// 我们将一个函数断言为 CreateArray 类型
const createArrayFn: CreateArray = (input) => {
return [input, input];
};
// 当我们"调用"这个函数时,T 才被推断出来
const numberArray = createArrayFn(100); // T 被推断为 number
const stringArray = createArrayFn("world"); // T 被推断为 string
createStoreImpl
:核心实现
createStoreImpl
是实际创建 store
的函数,它必须接收一个 creator
函数作为参数。
ini
type CreateStoreImpl = <
T,
Mos extends [StoreMutatorIdentifier, unknown][] = [],
>(
initializer: StateCreator<T, [], Mos>,
) => Mutate<StoreApi<T>, Mos>;
其实现逻辑非常简洁,是 zustand
底层发布-订阅模式的核心:
typescript
const createStoreImpl: CreateStoreImpl = (createState) => {
// 推断出状态类型 TState
type TState = ReturnType<typeof createState>;
type Listener = (state: TState, prevState: TState) => void;
// 闭包中维护的内部状态
let state: TState;
const listeners: Set<Listener> = new Set();
const setState: StoreApi<TState>['setState'] = (partial, replace) => {
// 获取下一个状态,支持函数式更新
const nextState =
typeof partial === 'function'
? (partial as (state: TState) => TState)(state)
: partial;
// 仅当状态发生变化时才更新并通知订阅者
if (!Object.is(nextState, state)) {
const previousState = state;
// 根据 replace 标志决定是替换还是合并状态
state =
(replace ?? (typeof nextState !== 'object' || nextState === null))
? (nextState as TState)
: Object.assign({}, state, nextState);
// 通知所有订阅者
listeners.forEach((listener) => listener(state, previousState));
}
};
// ... 创建 api 对象并初始化状态
};
核心逻辑解读:
- 闭包状态 :
state
和listeners
被保存在闭包中,构成了store
的私有状态。 setState
实现:
-
- 它首先计算出
nextState
。 - 使用
Object.is
进行浅比较,判断状态是否真的发生了变化。这是性能优化的关键,避免了不必要的更新。 replace ?? ...
逻辑决定了更新行为:
- 它首先计算出
-
-
- 如果
replace
被显式传递,则遵循其值。 - 如果
replace
未传递(为null
或undefined
),则会检查nextState
是否为对象。如果不是对象(如number
,string
等),则强制进行替换;如果是对象,则通过Object.assign
进行浅合并。
- 如果
-
-
- 最后,遍历
listeners
集合,调用所有订阅者函数,将新旧状态传递给它们。
- 最后,遍历
zustand
内部的事件发布-订阅机制设计得非常简单。这并非疏忽,而是因为 zustand
巧妙地将状态更新的调度工作委托给了 React 框架本身(通过 useSyncExternalStore
),我们将在下一节详细探讨。
四、React 集成层:create
与 useStore
zustand/react
模块提供了将核心 store
与 React 组件连接起来的桥梁。其入口是 create
函数,它是一个比 createStore
更高层次的封装。
create
函数:连接 Store 与 React
create
函数的定义与 createStore
类似,同样运用了柯里化和泛型推导。
javascript
// react.ts
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create;
它在内部调用 createImpl
,这个函数是集成逻辑的核心:
typescript
// react.ts
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
// 1. 使用我们之前分析的 createStore 创建一个底层的 store api
const api = createStore(createState);
// 2. 创建一个在组件中使用的 Hook
const useBoundStore: any = (selector?: any) => useStore(api, selector);
// 3. 将底层 api 的方法(getState, setState, subscribe等)附加到 Hook 上
Object.assign(useBoundStore, api);
return useBoundStore;
};
这段代码揭示了我们日常使用的 useUserStore
这样的 Hook 是如何诞生的:
- 它首先通过
createStore
创建了一个纯粹的、与框架无关的store
实例 (api
)。 - 然后,它定义了
useBoundStore
这个 Hook,其内部调用了useStore
。 - 最巧妙的是
Object.assign(useBoundStore, api)
。这使得useBoundStore
既是一个可以在组件中使用的 Hook,又是一个包含了getState
,setState
等方法的对象 。这就是为什么我们可以在组件外部通过useUserStore.getState()
来访问状态。
useStore
与 useSyncExternalStore
:安全订阅外部状态
useStore
是连接 zustand
和 React 的关键,它的核心是 React 18 引入的 useSyncExternalStore
Hook。
scss
// react.ts
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>,
selector: (state: TState) => StateSlice = identity as any,
) {
const slice = React.useSyncExternalStore(
api.subscribe,
React.useCallback(() => selector(api.getState()), [api, selector]),
React.useCallback(() => selector(api.getInitialState()), [api, selector]),
);
React.useDebugValue(slice);
return slice;
}
为什么需要 useSyncExternalStore
?
React 的 Fiber 架构允许渲染过程被中断和重启。当组件的状态由 React 自身管理时(如 useState
),React 能保证数据的一致性。但当状态来自外部系统 (如 zustand
store、localStorage
、甚至一个普通的全局变量)时,就可能出现**状态撕裂(State Tearing)**的问题。
例子 :一个组件在渲染时从外部 store
读取了值为 5
。此时,渲染被中断。在渲染重启之前,外部 store
的值变成了 6
。当组件恢复渲染时,它可能会获取到混乱的数据,导致 UI 与状态不一致。
useSyncExternalStore
就是为了解决这个问题而生的。它提供了一个安全的 API,让 React 能够订阅外部数据源,并确保在并发渲染模式下数据的一致性。
useSyncExternalStore
接受三个核心参数:
subscribe
(api.subscribe
):订阅函数 。React 会将一个内部的forceUpdate
函数传递给它。当外部状态变化时(即zustand
的setState
被调用并通知订阅者时),这个forceUpdate
会被触发,从而调度一个新的 React 渲染。getSnapshot
(() => selector(api.getState())
):获取快照函数 。这个函数的作用是从外部数据源获取当前状态的快照 。React 会在渲染开始时调用它,并用其返回值作为组件的当前状态。它还会用Object.is
比较前后两次快照的结果,如果相同则跳过更新,以优化性能。getServerSnapshot
(() => selector(api.getInitialState())
):获取服务端快照函数 。这个参数主要用于解决**服务器端渲染(SSR)和客户端水合(Hydration)**时不一致的问题。
例子 :一个用户的登录状态保存在 localStorage
中。在服务端渲染时,由于无法访问 localStorage
,服务器只能生成一个"未登录"状态的 HTML。然而,当客户端接收到 HTML 并进行水合时,它会从 localStorage
读取到"已登录"状态,导致服务端和客户端的初始 UI 不匹配,出现闪烁。
getServerSnapshot
允许我们在服务端提供一个权威的状态快照(例如,通过解析请求头中的 Cookie 来判断登录状态),确保服务端生成的 HTML 与客户端的初始状态一致。
useSyncExternalStore
的工作流程可以概括为:
- 初始渲染 :调用
getSnapshot
获取初始状态,并使用subscribe
订阅外部store
的变化。 - 外部状态更新 :
zustand
的setState
触发subscribe
注册的回调(即 React 的forceUpdate
)。 - React 调度更新:React 将组件标记为需要重渲染。
- 重渲染 :React 再次调用
getSnapshot
获取最新的状态,并与上一次的快照比较。如果不同,则更新组件,UI 展示最新状态。
通过这个机制,zustand
将状态管理的逻辑与 React 的渲染调度解耦,实现了高效、安全的状态同步。
React 相关的类型定义
在 react.ts
中,类型定义也相应地变得更加具体。
scala
// 限制只能访问 store 的只读方法,防止在组件中意外调用 setState
type ReadonlyStoreApi<T> = Pick<
StoreApi<T>,
'getState' | 'getInitialState' | 'subscribe'
>;
// 我们日常使用的 store hook 的完整类型
export type UseBoundStore<S extends ReadonlyStoreApi<unknown>> = {
// 调用时不带 selector,返回整个 state
(): ExtractState<S>;
// 调用时带 selector,返回 state 的一部分
<U>(selector: (state: ExtractState<S>) => U): U;
} & S; // & S 使得 hook 本身也挂载了 getState 等方法
// create 函数的完整类型签名
type Create = {
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
): UseBoundStore<Mutate<StoreApi<T>, Mos>>;
// ... 柯里化重载
};
五、中间件实现:以 persist
为例
中间件的核心思想是对 set
、 get
和 api
对象进行增强 。我们以 zustand
最常用的 persist
中间件为例,深入剖C析其实现细节。
persist
中间件:功能与配置
persist
中间件用于将 store
的状态持久化到存储介质(如 localStorage
或 AsyncStorage
)中。它提供了丰富的配置项来满足各种复杂场景。
javascript
const useUserStore = create(
persist(
// 1. 原始的状态创建函数
(set, get) => ({
// ... state definition
}),
// 2. 持久化配置对象
{
name: 'user-storage-v2', // 存储的 key (必填)
storage: createJSONStorage(() => customAsyncStorage), // 存储介质
partialize: (state) => ({ ... }), // 筛选需要持久化的字段
version: 2, // 当前状态版本
migrate: (persistedState, version) => { ... }, // 版本迁移函数
merge: (persistedState, currentState) => { ... }, // 自定义状态合并规则
onRehydrateStorage: (state) => { ... }, // 水合回调
// skipHydration: false, // 跳过自动水合
}
)
);
核心配置项解读:
name
: 在存储介质中使用的唯一键名。storage
: 指定存储介质,默认为localStorage
。zustand
提供了createJSONStorage
工具函数来包装存储引擎。partialize
: 一个函数,用于从完整状态中筛选出需要持久化的部分。这对于排除临时状态(如isEditing
)非常有用。version
&migrate
: 用于状态迁移。当你的应用更新,数据结构发生变化时,migrate
函数可以帮助你将旧版本的数据平滑地过渡到新结构。merge
: 定义从存储中恢复的状态(persistedState
)如何与当前代码中的初始状态(currentState
)合并。默认是浅合并。
工具函数
在深入 persist
的核心逻辑之前,我们需要了解两个关键的工具函数。
toThenable
:统一同步与异步操作
存储介质可能是同步的(如 localStorage
)或异步的(如 IndexedDB
)。为了用一套统一的逻辑来处理这两种情况,persist
使用了 toThenable
函数,它能将任何值或函数调用结果包装成一个类似 Promise 的对象(Thenable)。
javascript
const toThenable = (fn) => (input) => {
try {
const result = fn(input);
// 如果结果本身就是 Promise,直接返回
if (result instanceof Promise) {
return result;
}
// 否则,包装成一个 Thenable 对象
return {
then(onFulfilled) {
// 在 .then 中继续链式调用
return toThenable(onFulfilled)(result);
},
catch(_onRejected) {
return this;
},
};
} catch (e) {
// 捕获同步错误,并将其转换到 catch 链
return {
then(_onFulfilled) {
return this;
},
catch(onRejected) {
return toThenable(onRejected)(e);
},
};
}
};
这个函数通过柯里化和闭包,巧妙地模拟了 Promise 的链式调用和错误处理机制,使得后续的 hydrate
流程可以像处理 Promise 一样,通过 .then()
和 .catch()
来编排同步和异步操作。
createJSONStorage
:简化存储操作
这个函数包装了标准的 Storage API,自动处理了 JSON.stringify
和 JSON.parse
的过程,并兼容同步和异步的 getItem
方法。
javascript
export function createJSONStorage(getStorage, options = {}) {
let storage;
try {
storage = getStorage();
} catch {
// 在 SSR 等环境下,存储可能不可用
return;
}
return {
getItem: (name) => {
const parse = (str) => {
if (str === null) return null;
return JSON.parse(str, options.reviver);
};
const str = storage.getItem(name) ?? null;
// 如果 storage.getItem 返回的是 Promise,则链式调用 parse
if (str instanceof Promise) {
return str.then(parse);
}
// 否则直接解析
return parse(str);
},
setItem: (name, newValue) => {
return storage.setItem(name, JSON.stringify(newValue, options.replacer));
},
removeItem: (name) => storage.removeItem(name),
};
}
persistImpl
:核心实现逻辑
persist
中间件的实现可以分为两个主要部分:状态持久化( setItem
) 和 状态水合( hydrate
) 。
dart
const persistImpl = (config, baseOptions) => (set, get, api) => {
// ... 初始化 options
// 1. 定义核心的 setItem 函数
const setItem = () => {
// 使用 partialize 筛选要存储的状态
const stateToPersist = options.partialize({ ...get() });
// 使用 storage 引擎存储
return storage.setItem(options.name, {
state: stateToPersist,
version: options.version,
});
};
// 2. 重写 setState 和 creator 函数的 set 方法
// 使得每次状态更新后都会自动调用 setItem
const savedSetState = api.setState;
api.setState = (state, replace) => {
savedSetState(state, replace);
return setItem();
};
const configResult = config(
(...args) => {
set(...args);
return setItem();
},
get,
api
);
// ... hydrate 逻辑
return configResult;
};
状态水合 (hydrate
)
水合(Hydration)是 persist
中间件最复杂也最重要的部分。它负责在应用启动时,从存储介质中读取数据,并将其恢复到 store
中。整个过程被精心编排在一个 toThenable
链中。
javascript
const hydrate = () => {
if (!storage) return;
// ... 触发 onHydrate 回调
return toThenable(storage.getItem.bind(storage))(options.name)
// 步骤 1: 读取、迁移数据
.then((deserializedStorageValue) => {
if (deserializedStorageValue) {
// 版本不匹配,执行 migrate 函数
if (
typeof deserializedStorageValue.version === 'number' &&
deserializedStorageValue.version !== options.version
) {
if (options.migrate) {
// ... 执行 migrate 逻辑,可能返回 Promise
return [true, migratedState]; // [migrated, state]
}
} else {
// 版本一致,直接使用存储的状态
return [false, deserializedStorageValue.state];
}
}
// 无存储数据
return [false, undefined];
})
// 步骤 2: 合并状态并更新 store
.then(([migrated, migratedState]) => {
// 使用 merge 函数合并存储状态与当前状态
stateFromStorage = options.merge(migratedState, get() ?? configResult);
// 强制替换 store 的状态
set(stateFromStorage, true);
// 如果执行了迁移,则将迁移后的新状态写回存储
if (migrated) {
return setItem();
}
})
// 步骤 3: 完成处理
.then(() => {
// ... 触发 onFinishHydration 回调
})
.catch((e) => {
// ... 错误处理
});
};
暴露控制 API
最后,persist
中间件还通过 api.persist
向外暴露了一系列控制方法,允许我们在应用中手动控制持久化行为。
javascript
api.persist = {
// 动态更新配置
setOptions: (newOptions) => { ... },
// 清空存储
clearStorage: () => { ... },
// 手动触发水合
rehydrate: () => hydrate(),
// 检查是否已水合
hasHydrated: () => hasHydrated,
// 监听水合开始/结束
onHydrate: (cb) => { ... },
onFinishHydration: (cb) => { ... },
};
通过以上分析,我们可以看到 persist
中间件如何通过重写 setState
、利用 toThenable
统一异步流程、以及精巧的 hydrate
逻辑,实现了一个功能强大且配置灵活的数据持久化方案。