点击关注公众号,“技术干货” 及时达!
前言
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 实例。
参考资料
两张图带你全面了解 React 状态管理库:zustand 和 jotai
Zustand 源码浅析
看完zustand源码后,我的TypeScript水平突飞猛进。
总结
zustand 通过一个简单的状态存储对象 (store) 和函数式的 API 提供了灵活的状态管理方式。它的核心特点是轻量、灵活、易于集成,并通过 useSyncExternalStore 实现高效的状态订阅和组件重渲染机制。此外,zustand 还支持中间件扩展功能,如日志记录、持久化和调试工具集成,使其适应现代 React 的并发模式和服务器端渲染(SSR)的需求。
最后贴上 Zustand 的实现流程图:
通过学习 Zustand 源码,大概率能够提升你的 ts 技能,赶紧学起来吧。
最后分享两个我的两个开源项目,它们分别是:
前端脚手架 create-neat 在线代码协同编辑器
这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗
点击关注公众号,“技术干货” 及时达!