zustand原理
众所周知,前端状态管理库有两种模式,基于函数式编程思想的redux,一种是基于响应式编程思想的mobx。如今最火并被称为redux继任者的zustand,运用了什么样的原理呢?
发布订阅者模式
首先我们说到发布订阅者模式 发布者 => 事件中心 => 【 订阅者1,订阅者2,订阅者3】 订阅者不知道是谁发布的,也不知道还有哪些订阅者,他们之间完全解耦。
这里看一下zustand是怎么使用的(官网例子)。
//创建仓库(事件中心)
import { create } from 'zustand'
const useBear = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))
//订阅者
function BearCounter() {
const bears = useBear((state) => state.bears)
return <h1>{bears} bears around here...</h1>
}
//发布者
function Controls() {
const increasePopulation = useBear((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one upbutton
我们可以看到zustand的核心是发布订阅者模式,那么react又是如何知道状态更改需要更新的呢?
useSyncExternalStore
官方文档
useSyncExternalStore 是一个让你订阅外部 store 的 React Hook。
虽然react推荐在内部使用react useContext和useReducer维护状态,但是如果你考虑在外部维护,可以使用useSyncExternalStorehooks。
所以zustand就干了两件事
- 可以生成一个发布订阅者模式的仓库的创建器
- 和react相关联
代码模拟
function createStore(createState) {
let state;
const listeners = new Set();//存放想听状态变化通知"的函数的集合
const setState = (partial) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
if (Object.is(nextState, state)) return;
const previousState = state;
state = Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
};//支持传递数据还是函数,如果是函数就把state作为入参传递过去
const getState = () => state;
const subscription = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, subscription };
state = createState(setState, getState, api);// 执行用户的初始化函数
return api;
}
export default createStore;
其中listeners是核心,主要在数据变化发布通知、组件订阅、取消订阅的时候使用
subscription作为添加订阅者和当组件卸载的时候删除订阅者。
import { useSyncExternalStore } from "react";
import createStore from "./vanilla";
function useStore(api, selector) {
//selector 可选择部分函数
const getSnapshot = () => {
const state = api.getState();
return selector ? selector(state) : state;
};
//snapshot 当前瞬间的状态值
return useSyncExternalStore(api.subscription, getSnapshot, getSnapshot);
}
//创建 React 可用的 store,返回一个"两用"的 Hook(既能订阅,又能直接操作)。
function create(createState) {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
}
export default create;
useStore利用useSyncExternalStore能够使store接入react。
此时就已经可以使用了
核心源码
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
}
export type ExtractState<S> = S extends { getState: () => infer T } ? T : never
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 }
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-object-type
export interface StoreMutators<S, A> {}
export type StoreMutatorIdentifier = keyof StoreMutators<unknown, unknown>
type CreateStore = {
<T, Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
): Mutate<StoreApi<T>, Mos>
<T>(): <Mos extends [StoreMutatorIdentifier, unknown][] = []>(
initializer: StateCreator<T, [], Mos>,
) => Mutate<StoreApi<T>, Mos>
}
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
const listeners: Set<Listener> = new Set()
const setState: StoreApi<TState>['setState'] = (partial, replace) => {
// TODO: Remove type assertion once https://github.com/microsoft/TypeScript/issues/37663 is resolved
// https://github.com/microsoft/TypeScript/issues/37663#issuecomment-759728342
const nextState =
typeof partial === 'function'
? (partial as (state: TState) => TState)(state)
: partial
if (!Object.is(nextState, state)) {
const previousState = state
state =
(replace ?? (typeof nextState !== 'object' || nextState === null))
? (nextState as TState)
: Object.assign({}, state, nextState)
listeners.forEach((listener) => listener(state, previousState))
}
}
const getState: StoreApi<TState>['getState'] = () => state
const getInitialState: StoreApi<TState>['getInitialState'] = () =>
initialState
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
}
export const createStore = ((createState) =>
createState ? createStoreImpl(createState) : createStoreImpl) as CreateStore