zustand 是一个非常轻量的 react 状态管理库,借力了 react hooks,源码非常精简,使用方法也很简单。
store 创建
zustand 通过调用 create 方法创建 store,以 create 方法为入口来看下 store 的创建逻辑。
export const create = (<T>(createState: StateCreator<T, [], []> | undefined) =>
createState ? createImpl(createState) : createImpl) as Create;
在上面的代码中 createState 是一个可选参数,它的类型为 StateCreator<T, [], []> | undefined
。
-
StateCreator<T, [], []>
:这是一个函数类型,负责创建状态的初始值。StateCreator 通常是一个函数,用于定义状态的形状和行为。它可以包括初始状态以及状态操作的定义。 -
undefined
:因为 createState 是一个可选参数,所以它也可以是 undefined。
如果 createState 存在(即传入了状态创建函数),则调用 createImpl(createState)。createImpl 是一个内部实现的函数,用于根据 createState 创建状态。如果 createState 不存在(即未传入状态创建函数),则直接返回 createImpl 函数本身。这种情况通常用于对状态管理逻辑进行进一步的配置。
这段代码的作用是根据传入的 createState(如果有的话)来创建状态对象,并返回一个状态管理函数。通过使用泛型和类型断言,这个 create 函数能够灵活地支持各种类型的状态,并且在没有传入 createState 时,提供了一种配置状态管理的方式。
不传入 createState 的主要用途是在需要更高的灵活性时,通过获取底层的 createImpl 函数来实现自定义的状态初始化和扩展。这种方式能够在应用中支持多种复杂的状态管理需求,如延迟初始化、动态创建、多实例化以及中间件集成等。
StateCreator 类型
StateCreator 这个类型设计到的比较多,我们在学习 Zustand 的同时顺便把 ts 也学习了:
type SetStateInternal<T> = {
_(
partial: T | Partial<T> | { _(state: T): T | Partial<T> }["_"],
replace?: false
): void;
_(state: T | { _(state: T): T }["_"], replace: true): void;
}["_"];
export interface StoreApi<T> {
setState: SetStateInternal<T>;
getState: () => T;
getInitialState: () => T;
subscribe: (listener: (state: T, prevState: T) => void) => () => void;
}
type Get<T, K, F> = K extends keyof T ? T[K] : F;
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;
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 };
export interface StoreMutators<S, A> {}
export type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>;
SetStateInternal<T>
类型
type SetStateInternal<T> = {
_(
partial: T | Partial<T> | { _(state: T): T | Partial<T> }["_"],
replace?: false
): void;
_(state: T | { _(state: T): T }["_"], replace: true): void;
}["_"];
SetStateInternal<T>
定义了一种内部更新状态的方法。['_']
是一种获取重载类型的技巧,用来使 setState 支持两种更新方式:
-
_(partial: T | Partial<T> | { _(state: T): T | Partial<T> }['_'], replace?: false): void
:参数 partial 可以是完整的状态对象 T,部分状态Partial<T>
,或者是一个函数,返回新的状态或部分状态。replace 是一个可选的布尔值,如果为 false 或不传入,则表示部分更新。 -
_(state: T | { _(state: T): T }['_'], replace: true): void
:当 replace 为 true 时,表示完全替换状态。参数 state 是新的完整状态。
在 TypeScript 中,函数参数具有逆变的特性。这意味着 _
方法的参数类型越宽泛,越可以被其他函数类型替换。对于 SetStateInternal 类型的 _
方法:
-
第一个参数 partial 或 state 被设计为接受多种类型(T、
Partial<T>
、或者函数形式的状态更新)。这里体现了逆变的特性,因为接受一个更宽泛的类型集合。 -
例如,如果 T 是
{ count: number }
,那么Partial<T>
是{ count?: number }
,而{ (state: T): T | Partial<T> }["_"]
是更复杂的类型。这些参数类型从具体类型到较宽泛的类型,符合逆变的定义。
对于返回类型 void,函数的重载都返回 void,没有任何变形的体现。在这个例子中,返回类型没有实际的协变或逆变意义。
在 TypeScript 的函数参数中,默认情况下参数是双变的。这意味着参数既可以是它的子类型,也可以是它的超类型。在 SetStateInternal 的实现中,函数的参数默认支持双变性,这使得类型系统更灵活。
例如,对于 _
方法的第一个重载:partial 可以是类型 T,也可以是 Partial<T>
,这实际上就是利用了 TypeScript 的双变性,允许我们传入更宽泛或更具体的类型。
假设我们有一个简单的状态结构:
interface State {
count: number;
name: string;
}
const setState: SetStateInternal<State> = (partial, replace) => {
if (replace) {
// 完全替换状态
console.log("Replacing state with:", partial);
} else {
// 部分更新状态
console.log("Updating state with:", partial);
}
};
// 使用部分更新
setState({ count: 10 }, false); // 输出: Updating state with: { count: 10 }
// 使用完全替换
setState({ count: 20, name: "Alice" }, true); // 输出: Replacing state with: { count: 20, name: 'Alice' }
// 使用状态更新函数
setState((state) => ({ count: state.count + 1 }), false); // 输出: Updating state with: function
在这个例子中,SetStateInternal<T>
可以根据参数来决定如何更新状态,支持多种形式的状态更新。
Get<T, K, F>
类型
type Get<T, K, F> = K extends keyof T ? T[K] : F;
Get 是一个条件类型,用于从类型 T 中提取键 K 的类型。如果 K 存在于 T 中,则返回 T[K]
;否则,返回默认类型 F。常用于根据键名动态获取类型,且可以在键不存在时提供一个默认类型。
如下示例所示:
interface User {
id: number;
name: string;
}
type UserNameType = Get<User, "name", string>; // 返回类型为 string
type UserAgeType = Get<User, "age", number>; // 返回类型为 number(因为 'age' 不存在)
Mutate<S, Ms>
类型
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;
Mutate<S, Ms>
是一个递归类型,用于对状态类型 S 应用一系列的“变换器”(mutators)
Ms 是一个数组类型,其中每个元素表示一个 [Mi, Ma] 元组:
-
Mi 是变换器的标识符。
-
Ma 是变换器的参数类型。
我们再来对这个 ts 类型做一个详细的讲解:
-
number extends Ms['length' & keyof Ms]
:检查 Ms 是否为一个动态长度的数组。如果是,则返回原始状态类型 S。 -
Ms extends []
:如果 Ms 是空数组,则表示没有变换,返回原始状态 S。 -
Ms extends [[infer Mi, infer Ma], ...infer Mrs]
:提取第一个 mutator 元组 [Mi, Ma],剩余的存入 Mrs。使用StoreMutators<S, Ma>[Mi & StoreMutatorIdentifier]
对状态类型进行变换。递归调用 Mutate,继续对剩余的 mutators 进行处理。
我们再来看一下下面的 ts 类型定义:
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;
// 2. 定义 StoreMutatorIdentifier 类型,用于获取 StoreMutators 键名
export type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>;
interface StoreMutators<S, A> {
logging: (state: S, action: A) => S;
timestamp: (state: S, action: A) => S;
}
type LoggingMutator = ["logging", void];
type TimestampMutator = ["timestamp", void];
// 应用多个 Mutator
type TransformedState = Mutate<
{ count: number },
[LoggingMutator, TimestampMutator]
>;
在 Zustand 中,这段代码用于类型级别的状态变换,用来管理和应用各种 "Mutator"。Mutator 是一种状态变换器,可以通过特定的方式修改状态。通过这段代码,Zustand 能够:
-
增强状态功能:可以为状态增加一些额外的功能,比如日志记录、时间戳、撤销/重做等功能。这些增强功能可以通过不同的 Mutator 来实现。
-
支持插件式扩展:不同的 Mutator 可以看作是对状态管理的插件,通过 Mutate 类型,Zustand 可以在类型级别上灵活地应用多个 Mutator。这种设计允许开发者根据需求选择和组合不同的功能,从而增强状态管理的能力。
-
实现类型安全的状态变换:通过 Mutate 类型的递归应用,Zustand 能够确保状态的类型在经过多个变换器后保持正确性,避免在使用状态时出现类型不匹配的问题。
我们再来看一下下面这段代码的使用示例:
// 1. 定义 Mutate 类型,用于递归地应用变换器数组 Ms 到状态类型 S
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;
// 2. 定义 StoreMutatorIdentifier 类型,用于获取 StoreMutators 键名
export type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>;
// 3. 定义 StoreMutators 接口,其中包含两个变换器 logging 和 timestamp
interface StoreMutators<S, A> {
logging: (state: S, action: A) => S;
timestamp: (state: S, action: A) => S;
}
// 4. 定义 LoggingMutator 和 TimestampMutator 类型,分别表示一个 mutator 及其参数类型
type LoggingMutator = ["logging", void];
type TimestampMutator = ["timestamp", void];
// 5. 实际的 mutators 实现,包含 logging 和 timestamp 的逻辑
const mutators: StoreMutators<any, any> = {
logging: (state, action) => {
console.log("Logging state:", state);
return state; // 返回未修改的状态
},
timestamp: (state, action) => {
return {
...state,
timestamp: new Date().toISOString(), // 添加时间戳
};
},
};
// 6. 定义 applyMutators 函数,用于依次应用给定的变换器列表
function applyMutators<S, Ms extends [StoreMutatorIdentifier, unknown][]>(
initialState: S,
mutatorsList: Ms
): Mutate<S, Ms> {
let state = initialState;
for (const [mutatorId, action] of mutatorsList) {
const mutator = mutators[mutatorId as StoreMutatorIdentifier];
if (mutator) {
state = mutator(state, action);
}
}
return state as Mutate<S, Ms>;
}
// 7. 使用 applyMutators 函数对状态进行变换
const initialState = { count: 0 };
// 定义变换器列表,并使用 LoggingMutator 和 TimestampMutator 类型进行类型约束
const transformedState = applyMutators<
typeof initialState,
[LoggingMutator, TimestampMutator]
>(initialState, [
["logging", undefined],
["timestamp", undefined],
]);
console.log("Transformed state:", transformedState);
在上面的这种代码展示了一种状态管理系统的设计模式,特别适用于在应用中需要动态地修改、增强或扩展状态的场景。通过使用"变换器"(mutators),可以对状态进行按需的修改,同时保持类型安全。
目前这段代码实现的功能,更接近于插件系统的设计。通过定义变换器(mutators),我们可以动态地对状态进行增强或修改,这种机制类似于为状态管理添加插件。通过传入不同的变换器列表(mutators),可以在状态管理过程中动态地添加或移除功能。例如,我们可以根据需求添加日志记录、时间戳等功能,也可以根据不同的应用场景使用不同的变换器组合。
我们再来实现一个中间件的案例,我们需要了解一下中间件的特点,在经典的中间件模式中:
-
中间件可以拦截状态的变化,并在处理前后执行一些逻辑。
-
每个中间件都可以选择是否继续传递控制权给下一个中间件。
-
中间件的执行顺序通常是按定义的顺序依次执行。
为了让当前代码更符合中间件的特点,可以引入以下改进:
-
增加中间件控制权:允许中间件决定是否继续执行下一个中间件。
-
添加 next 函数:为每个中间件提供一个 next 函数,使其可以显式地调用下一个中间件。
下面是对现有代码的修改,以实现一个更符合中间件模式的版本:
// 1. 定义 Mutate 类型,用于递归地应用变换器数组 Ms 到状态类型 S
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;
// 2. 定义 StoreMutatorIdentifier 类型,用于获取 StoreMutators 键名
export type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>;
// 3. 定义 StoreMutators 接口,其中包含两个变换器 logging 和 timestamp
interface StoreMutators<S, A> {
logging: (state: S, action: A, next: (newState: S) => S) => S;
timestamp: (state: S, action: A, next: (newState: S) => S) => S;
}
// 4. 定义 LoggingMutator 和 TimestampMutator 类型,分别表示一个 mutator 及其参数类型
type LoggingMutator = ["logging", void];
type TimestampMutator = ["timestamp", void];
// 5. 实际的 mutators 实现,包含 logging 和 timestamp 的逻辑
const mutators: StoreMutators<any, any> = {
logging: (state, action, next) => {
console.log("Logging state:", state);
return next(state); // 调用 next 并传入当前状态
},
timestamp: (state, action, next) => {
const newState = {
...state,
timestamp: new Date().toISOString(), // 添加时间戳
};
return next(newState); // 使用更新后的状态调用 next
},
};
// 6. 定义 applyMiddlewares 函数,用于按顺序执行中间件列表
function applyMiddlewares<S, Ms extends [StoreMutatorIdentifier, unknown][]>(
initialState: S,
middlewaresList: Ms
): Mutate<S, Ms> {
let index = 0; // 当前中间件的索引
// 定义一个递归函数,依次调用每个中间件
const execute = (state: S): S => {
if (index >= middlewaresList.length) {
return state; // 所有中间件执行完毕,返回最终的状态
}
const [mutatorId, action] = middlewaresList[index++];
const mutator = mutators[mutatorId as StoreMutatorIdentifier];
if (mutator) {
// 调用当前的中间件,并传入 next 函数
return mutator(state, action, (newState) => execute(newState));
}
return execute(state); // 如果没有找到 mutator,则继续执行下一个
};
return execute(initialState) as Mutate<S, Ms>;
}
// 7. 使用 applyMiddlewares 函数对状态进行变换
const initialState = { count: 0 };
// 定义中间件列表,并使用 LoggingMutator 和 TimestampMutator 类型进行类型约束
const transformedState = applyMiddlewares<
typeof initialState,
[LoggingMutator, TimestampMutator]
>(initialState, [
["logging", undefined],
["timestamp", undefined],
]);
console.log("Transformed state:", transformedState);
最终输出结果如下所示:
在上面的代码的执行流程中:
-
logging 中间件打印初始状态 { count: 0 },并将状态传递给下一个中间件。
-
timestamp 中间件在状态中添加 timestamp 字段,然后将新的状态传递给下一个中间件。
-
最终,变换后的状态是 { count: 0, timestamp: "2024-10-10T10:00:00.000Z" }。
通过这些修改,现在的中间件系统可以按顺序对状态进行变换,每个中间件都可以对状态进行修改,并将修改后的状态传递给下一个中间件。这使得系统更符合中间件的设计模式,具有更强的灵活性和扩展性。
StateCreator
StateCreator 定义了一个函数签名,用于创建状态的函数。
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 };
我们先来对泛型参数来做一个简单的解释:
-
T: 初始状态的类型。
-
Mis: 输入的 mutators 列表(默认值为空数组)。这些 mutators 用于在创建状态时进行状态的变换或增强。
-
Mos: 输出的 mutators 列表(默认值为空数组)。用于表示经过变换后的状态增强列表。
-
U: 返回的状态类型,默认与 T 相同。这意味着状态创建函数可以返回一个不同类型的状态。
StateCreator 定义了一个函数签名,表示一个状态创建函数,接收以下三个参数:
-
setState: 用于更新状态的方法,其类型是从
Mutate<StoreApi<T>, Mis>
中获取的 setState。 -
getState: 用于获取当前状态的方法,类型同样来源于
Mutate<StoreApi<T>, Mis>
。 -
store: 经过所有输入的 mutators 变换后的 StoreApi 类型。
函数返回值为 U 类型的状态。这表示状态创建函数在初始化状态时可以执行自定义的逻辑,并返回一个不同的状态类型。
$$storeMutators
属性是可选的,用于存储变换器的元信息,即变换后的 mutators 列表。这为 zustand 提供了一种跟踪和管理状态增强的机制。
StateCreator 类型允许开发者在创建状态时传入多个 mutators,这些 mutators 会按顺序应用到状态管理的实例上,增强状态的功能。通过 Mutate 和 Get 等类型工具,StateCreator 可以动态地根据输入的 mutators 列表调整 setState 和 getState 方法的类型签名。
我们可以通过 Mis 和 Mos 来定义一个状态管理函数,该函数会使用输入的 mutators 进行状态的变换,并追踪输出的变换器列表。
下面是一个示例,演示如何使用带有 Mis 和 Mos 的状态创建函数,在创建状态时动态地增强状态管理实例。
import { create } from "zustand";
// 1. 定义变换器接口和类型
// 变换器接口,描述状态管理过程中可以使用的变换器
interface StoreMutators<S, A> {
logging: (state: S, action: A) => S;
timestamp: (state: S, action: A) => S;
}
// 变换器类型,用于描述各个变换器的具体参数
type LoggingMutator = ["logging", { level: "info" | "warn" | "error" }];
type TimestampMutator = ["timestamp", { format: "iso" | "unix" }];
// 2. 定义状态类型
interface State {
count: number;
timestamp?: string;
increment: () => void; // 添加 increment 方法的定义
}
// 3. 定义输入和输出的 mutators 列表
type Mis = [LoggingMutator, TimestampMutator]; // 输入的变换器列表
type Mos = [LoggingMutator, TimestampMutator]; // 输出的变换器列表
// 4. 实现具体的变换器逻辑
const mutators: StoreMutators<any, any> = {
logging: (state, action) => {
const { level } = action;
switch (level) {
case "info":
console.info(`Logging state change at level: ${level}`, state);
break;
case "warn":
console.warn(`Logging state change at level: ${level}`, state);
break;
case "error":
console.error(`Logging state change at level: ${level}`, state);
break;
default:
console.log(`Logging state change at level: ${level}`, state);
}
return state; // 不改变状态,仅记录日志
},
timestamp: (state, action) => {
const { format } = action;
const newTimestamp =
format === "iso" ? new Date().toISOString() : Date.now().toString();
return {
...state,
timestamp: newTimestamp, // 更新状态,添加时间戳
};
},
};
// 5. 创建状态管理函数,并使用带有 Mis 和 Mos 的状态创建函数
export const useStore = create<State>((set, get) => ({
count: 0,
increment: () => {
set((state) => {
// 应用 mutators
let newState = state;
newState = mutators.logging(newState, { level: "info" });
newState = mutators.timestamp(newState, { format: "iso" });
// 返回变更后的状态
return { ...newState, count: state.count + 1 };
});
},
}));
"use client";
import { useStore } from "@/store";
import React from "react";
function App() {
const { count, increment, timestamp } = useStore((state) => ({
count: state.count,
increment: state.increment,
timestamp: state.timestamp,
}));
console.log(count, timestamp);
return (
<div>
<p>Count: {count}</p>
<p>Timestamp: {timestamp}</p>
<button onClick={increment}>添加</button>
</div>
);
}
export default App;
现在,点击 添加
按钮时,计数器值会增加,并在控制台输出相应的日志信息,同时界面上会更新显示的 count 和 timestamp 值。
通过这个示例,我们展示了如何使用 zustand 的 Mis 和 Mos 来应用和追踪变换器对状态管理的增强。这样可以在创建状态时灵活地对状态管理系统进行扩展,使得状态管理更具灵活性和可扩展性
createImpl
create 函数讲解完成之后,我们顺着往下来到 createImpl 代码:
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api = createStore(createState);
const useBoundStore: any = (selector?: any) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
};
在 createImpl 里面最后返回了 useBoundStore 方法,这个方法基于 useStore 和 api:
- useStore: 基于 react hook 中的 useSyncExternalStore,react 状态管理库其核心之一就是状态改变时如何触发更新渲染,像 react-redux、或者原生 createContext 亦或者 forceUpdate,都是间接调用 setState 方法去触发更新,而 useSyncExternalStore 是官方提供的另一种状态更新方案,这个在文章的前文有相关的链接。
export function useStore<S extends ReadonlyStoreApi<unknown>, U>(
api: S,
selector: (state: ExtractState<S>) => U
): U;
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>,
selector: (state: TState) => StateSlice = identity as any
) {
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
);
React.useDebugValue(slice);
return slice;
}
- api 对象:调用 createStore 方法并传入 createState 参数得到 api 对象(这儿假定传入的 createState 是一个函数)。
调用 createStore ,实则是将 createState 参数透传给 createStoreImpl 方法(这儿同样忽略 createState 不存在的情况,源码里面之所以对 createState 不存在的情况下也做了处理,是为了让开发者自定义 store 而已)。
在 createStore 中调用的 createStoreImpl 方法同样定义在 src/vanilla.ts 文件中:
type CreateStoreImpl = <
T,
Mos extends [StoreMutatorIdentifier, unknown][] = []
>(
initializer: StateCreator<T, [], Mos>
) => Mutate<StoreApi<T>, Mos>;
const createStoreImpl: CreateStoreImpl = (createState) => {
type TState = ReturnType<typeof createState>;
type Listener = (state: TState, prevState: TState) => void;
let state: TState;
// listener函数集合,用Set结构保证同一个listener函数只添加一次,避免重复添加
const listeners: Set<Listener> = new Set();
// 更新state状态并调用注册到集合里面的所有listener,触发更新渲染,
// 至于调用listener如何触发的更新就涉及到react hook的useSyncExternalStore了
const setState: StoreApi<TState>["setState"] = (partial, replace) => {
// 如果partial是函数,则调用函数得到nextState,不然直接赋值partial给nextState
const nextState =
typeof partial === "function"
? (partial as (state: TState) => TState)(state)
: partial;
// 通过比对nextState和state决定是否更新state状态以及更新触发渲染
if (!Object.is(nextState, state)) {
const previousState = state;
// 如果replace为真值,直接替换原来的state,否则合并nextState到state
state =
replace ?? (typeof nextState !== "object" || nextState === null)
? (nextState as TState)
: Object.assign({}, state, nextState);
// 调用listener触发更新渲染
listeners.forEach((listener) => listener(state, previousState));
}
};
// 提供获取state的方法
const getState: StoreApi<TState>["getState"] = () => state;
// 获取初始状态。initialState 会在函数最后被赋值为状态的初始值。
const getInitialState: StoreApi<TState>["getInitialState"] = () =>
initialState;
// 添加listener,返回值提供删除listener的方法
// 其实这个subscribe会在react useEffect里面执行往Set集合里面添加listener,这个return对应的就是useEffect里面的return,
const subscribe: StoreApi<TState>["subscribe"] = (listener) => {
listeners.add(listener);
// Unsubscribe
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = (state = createState(setState, getState, api));
return api as any;
};
从 createStoreImpl
代码可以看出:通过 zustand 管理的状态 state 实则就是 createState 在通过 create(createState) ⇒ createImpl(createState) ⇒ createStore(createState) ⇒ createStoreImpl(createState)
透传后在 createStoreImpl 里面被执行后返回的那个返回值,而在 createStoreImpl 函数里面最后返回的并不是这个状态 state ,而是一个合并了更新 setState、访问 getState、订阅函数 subscribe、销毁函数 destroy 的 api 对象,管理的状态 state 实则就是以闭包的形式存在。
再回头看调用 create 创建的 store,这个 store 就是一个函数方法,对应 createImpl(createState) 里面的 useBoundStore(const useBoundStore = (selector, equalityFn) => useStore(api, selector, equalityFn),更准确的说是一个自定义的 react hook,这个在后面会阐述,和 redux 的 useSelector 使用方法是一样的),只是在这个函数方法上叠加了 api 对象的属性。
每一次调用 useXXX 都会去执行 create 函数,创建那么多份不会内存泄漏吗?
这个问题是引自另外一篇文章的评论区问题 看完 zustand 源码后,我的 TypeScript 水平突飞猛进。
既然看过源码,我能问个问题吗? 当你创建 store 是这样创建的
const useXXX = cteate(set=>{...})
这就意味着每一次调用 useXXX 都会去执行 create 函数,创建那么多份不会内存泄漏吗?还是说底层保证了每次调用都是那一份?能看看怎么保证的吗?
在 zustand 中,create 函数用于创建一个状态管理的 store,并返回一个 useStore 钩子函数。当你使用 const useXXX = create(set => {...})来创建 store 时,create 函数只会被执行一次,并且 zustand 的内部机制会确保每次调用 useXXX 时都是使用同一个 store 实例,而不会重复创建多个 store。因此,不会出现内存泄漏问题。
当你调用 create 函数时,zustand 会创建一个状态管理的实例,并返回一个用于访问状态的 useStore 钩子函数。create 函数的具体流程如下:
-
create 函数内部会调用一个函数(如 createStoreImpl),来实际创建并初始化一个状态管理的 store 实例。这个实例包含了所有管理状态的方法(如 setState、getState、subscribe 等),并会在内存中保持对这些方法的引用。
-
zustand 会通过一个闭包将这个 store 实例包装在 useStore 钩子函数中,并返回这个钩子函数。每次调用 useStore 时,都是访问同一个已经创建的 store 实例,而不会重新创建。
-
在调用 useStore 时,zustand 会使用 React 的 useSyncExternalStore 来确保组件与状态之间的订阅关系。通过 subscribe 方法,zustand 会将每个订阅的组件与状态的变化关联起来,当状态变化时,只有需要更新的组件会重新渲染。
create 函数只会执行一次,返回的 useStore 钩子函数会保持对 store 实例的引用。无论你在组件中使用多少次 useStore,访问的都是同一个 store 实例。并且它是通过 subscribe 方法,zustand 可以在组件卸载时自动移除监听器,防止订阅关系残留。当组件卸载时,zustand 会自动取消该组件的订阅,不会导致内存泄漏。
export function useStore<TState, StateSlice>(
api: ReadonlyStoreApi<TState>,
selector: (state: TState) => StateSlice = identity as any
) {
const slice = React.useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
);
React.useDebugValue(slice);
return slice;
}
因此 create 函数只会执行一次,并返回一个 useStore 钩子函数,该函数始终引用同一个 store 实例。这个 store 实例包含了所有的状态管理方法(如 setState、getState、subscribe 等),并且在内存中保存。
每次调用 useStore 时,通过内部的 useSyncExternalStore 钩子,管理与 store 的订阅。组件会订阅 store 的状态变化,当状态发生改变时,useSyncExternalStore 会触发重新渲染。无论组件中使用 useStore 调用多少次,它们访问的都是最初通过 create 创建的那个 store 实例。
参考资料
总结
zustand 通过一个简单的状态存储对象 (store) 和函数式的 API 提供了灵活的状态管理方式。它的核心特点是轻量、灵活、易于集成,并通过 useSyncExternalStore 实现高效的状态订阅和组件重渲染机制。此外,zustand 还支持中间件扩展功能,如日志记录、持久化和调试工具集成,使其适应现代 React 的并发模式和服务器端渲染(SSR)的需求。
最后贴上 Zustand 的实现流程图:
通过学习 Zustand 源码,大概率能够提升你的 ts 技能,赶紧学起来吧。
最后分享两个我的两个开源项目,它们分别是:
这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗