前置知识: TypeScript

类型安全的状态管理

00:00
3 min Advanced 2026/6/14

构建类型安全的状态管理

概述

状态管理是前端应用的核心架构之一。通过 TypeScript 的泛型和条件类型,可以构建类型安全的状态管理方案,确保状态结构、更新操作和派生数据都有精确的类型约束。本文介绍如何从零构建一个类型安全的 Store,涵盖状态定义、更新操作、选择器、中间件和异步操作等核心概念。

基础概念

Store:状态容器,持有应用的状态数据,提供读取和更新接口。Store 的状态类型在创建时确定,所有操作都受类型约束。

setState:状态更新方法,支持直接传入部分状态对象或更新函数。更新操作会与当前状态浅合并。

选择器(Selector):从 Store 状态派生计算的函数。选择器可以缓存计算结果,避免不必要的重渲染。

订阅(Subscribe):监听状态变化的机制。当状态更新时,所有订阅者都会收到通知

中间件(Middleware):拦截状态更新函数,可以实现日志记录持久化、异步操作等横切关注点。

快速上手

基础 Store

// 类型安全的 Store 接口
interface Store<S extends object> {
  getState(): S;
  setState(partial: Partial<S> | ((state: S) => Partial<S>)): void;
  subscribe(listener: (state: S) => void): () => void;
}

// 创建 Store
function createStore<S extends object>(initialState: S): Store<S> {
  let state = { ...initialState };
  const listeners = new Set<(state: S) => void>();

  return {
    getState: () => state,
    setState(partial) {
      const update = typeof partial === 'function' ? partial(state) : partial;
      state = { ...state, ...update };
      listeners.forEach((fn) => fn(state));
    },
    subscribe(listener) {
      listeners.add(listener);
      return () => listeners.delete(listener);
    },
  };
}

// 使用
interface AppState {
  count: number;
  user: { name: string; age: number } | null;
  theme: 'light' | 'dark';
}

const store = createStore<AppState>({
  count: 0,
  user: null,
  theme: 'light',
});

// 读取状态
store.getState().count; // number

// 更新状态
store.setState({ count: 1 });
store.setState((state) => ({ count: state.count + 1 }));

// 订阅变化
const unsubscribe = store.subscribe((state) => {
  console.log('状态变化:', state.count);
});

类型安全的选择器

// 选择器函数
function createSelector<S, R>(
  store: Store<S>,
  selector: (state: S) => R,
  equalityFn?: (a: R, b: R) => boolean
): () => R {
  let lastValue = selector(store.getState());

  // 订阅状态变化
  store.subscribe((state) => {
    const newValue = selector(state);
    if (equalityFn ? !equalityFn(lastValue, newValue) : lastValue !== newValue) {
      lastValue = newValue;
    }
  });

  return () => lastValue;
}

// 使用
const getCount = createSelector(store, (state) => state.count);
const getUserName = createSelector(store, (state) => state.user?.name ?? '未登录');

console.log(getCount()); // number
console.log(getUserName()); // string

详细用法

类型安全的 Action

// 定义 Action 类型
interface Action<T extends string, P = void> {
  type: T;
  payload: P;
}

// Action 创建器
function createAction<T extends string>(type: T): { type: T };
function createAction<T extends string, P>(type: T): { type: T; payload: P };
function createAction<T extends string, P>(type: T) {
  return (payload?: P) => ({ type, payload });
}

// 定义应用的 Actions
const increment = createAction<'increment', void>('increment');
const setUser = createAction<'setUser', { name: string; age: number }>('setUser');
const setTheme = createAction<'setTheme', 'light' | 'dark'>('setTheme');

// Reducer
type AppAction =
  | Action<'increment'>
  | Action<'setUser', { name: string; age: number }>
  | Action<'setTheme', 'light' | 'dark'>;

function reducer(state: AppState, action: AppAction): AppState {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'setUser':
      return { ...state, user: action.payload };
    case 'setTheme':
      return { ...state, theme: action.payload };
  }
}

异步操作

// 类型安全的异步操作
interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

function createAsyncState<T>(): AsyncState<T> {
  return { data: null, loading: false, error: null };
}

// 异步 Action
interface AsyncActions<T> {
  request: () => void;
  success: (data: T) => void;
  failure: (error: string) => void;
}

function createAsyncActions<T>(store: Store<{ async: AsyncState<T> }>): AsyncActions<T> {
  return {
    request: () =>
      store.setState((state) => ({
        async: { ...state.async, loading: true, error: null },
      })),
    success: (data) =>
      store.setState((state) => ({
        async: { data, loading: false, error: null },
      })),
    failure: (error) =>
      store.setState((state) => ({
        async: { ...state.async, loading: false, error },
      })),
  };
}

// 使用
async function fetchUser(id: string) {
  const actions = createAsyncActions<User>(userStore);
  actions.request();
  try {
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    actions.success(user);
  } catch (error) {
    actions.failure(error instanceof Error ? error.message : '未知错误');
  }
}

中间件

// 中间件类型
type Middleware<S> = (state: S, update: Partial<S>, next: (update: Partial<S>) => void) => void;

// 日志中间件
function loggerMiddleware<S>(
  state: S,
  update: Partial<S>,
  next: (update: Partial<S>) => void
): void {
  console.log('状态更新前:', state);
  console.log('更新内容:', update);
  next(update);
  console.log('状态更新后:', state);
}

// 持久化中间件
function persistMiddleware<S extends object>(key: string): Middleware<S> {
  return (state, update, next) => {
    next(update);
    // 更新后保存到 localStorage
    const newState = { ...state, ...update };
    localStorage.setItem(key, JSON.stringify(newState));
  };
}

// 增强版 Store
function createStoreWithMiddleware<S extends object>(
  initialState: S,
  middlewares: Middleware<S>[]
): Store<S> {
  const baseStore = createStore(initialState);

  return {
    ...baseStore,
    setState(partial) {
      const update = typeof partial === 'function' ? partial(baseStore.getState()) : partial;
      // 执行中间件链
      let index = 0;
      const next = (u: Partial<S>) => {
        if (index < middlewares.length) {
          middlewares[index++](baseStore.getState(), u, next);
        } else {
          baseStore.setState(u);
        }
      };
      next(update);
    },
  };
}

常见场景

表单状态管理

interface FormState<T> {
  values: T;
  errors: Partial<Record<keyof T, string>>;
  touched: Partial<Record<keyof T, boolean>>;
  isSubmitting: boolean;
}

function createFormStore<T extends Record<string, any>>(initialValues: T) {
  const store = createStore<FormState<T>>({
    values: initialValues,
    errors: {},
    touched: {},
    isSubmitting: false,
  });

  return {
    store,
    setFieldValue<K extends keyof T>(key: K, value: T[K]) {
      store.setState((state) => ({
        values: { ...state.values, [key]: value },
        touched: { ...state.touched, [key]: true },
      }));
    },
    setFieldError<K extends keyof T>(key: K, error: string) {
      store.setState((state) => ({
        errors: { ...state.errors, [key]: error },
      }));
    },
    reset() {
      store.setState({
        values: initialValues,
        errors: {},
        touched: {},
        isSubmitting: false,
      });
    },
  };
}

分片状态

// 将状态分片管理
interface Slice<S, A> {
  state: S;
  actions: A;
}

function createSlice<
  S extends object,
  A extends Record<string, (state: S, ...args: any[]) => Partial<S>>,
>(config: {
  initialState: S;
  actions: A;
}): Slice<
  S,
  {
    [K in keyof A]: A[K] extends (state: S, ...args: infer P) => any ? (...args: P) => void : never;
  }
> {
  const store = createStore(config.initialState);

  const actions = Object.fromEntries(
    Object.entries(config.actions).map(([key, action]) => [
      key,
      (...args: any[]) => {
        const update = action(store.getState(), ...args);
        store.setState(update);
      },
    ])
  ) as any;

  return { state: store.getState(), actions };
}

// 使用
const counterSlice = createSlice({
  initialState: { count: 0, step: 1 },
  actions: {
    increment: (state) => ({ count: state.count + state.step }),
    decrement: (state) => ({ count: state.count - state.step }),
    setStep: (state, step: number) => ({ step }),
  },
});

counterSlice.actions.increment(); // count + step
counterSlice.actions.setStep(5); // step = 5

注意事项

  • 可变更新:始终使用展开运算符或 immutable 更新库创建新状态对象,不要直接修改状态直接修改会导致订阅者无法检测变化
  • 合并:setState 使用浅合并嵌套对象需要手动展开于深层嵌套状态,考虑使用 immer 等简化不可变更新。
  • 选择器性能选择器应避免在每次调用时创建新对象(如数组对象字面量),否则会导致不必要的重渲染。使用浅比较函数优化
  • 循环更新:在订阅回调中调用 setState 可能导致无限循环。确保在订阅回调中有适当的条件判断。

进阶用法

时间旅行调试

// 支持时间旅行的 Store
function createHistoryStore<S extends object>(initialState: S) {
  const history: S[] = [initialState];
  let currentIndex = 0;

  const store = createStore(initialState);

  store.subscribe((state) => {
    // 记录每次状态变化
    history.splice(currentIndex + 1);
    history.push({ ...state });
    currentIndex = history.length - 1;
  });

  return {
    ...store,
    undo() {
      if (currentIndex > 0) {
        currentIndex--;
        store.setState(history[currentIndex]);
      }
    },
    redo() {
      if (currentIndex < history.length - 1) {
        currentIndex++;
        store.setState(history[currentIndex]);
      }
    },
    canUndo: () => currentIndex > 0,
    canRedo: () => currentIndex < history.length - 1,
  };
}

计算属性

// 类型安全的计算属性
interface ComputedState<S, C extends Record<string, (state: S) => any>> {
  getState(): S & { [K in keyof C]: ReturnType<C[K]> };
  setState(partial: Partial<S> | ((state: S) => Partial<S>)): void;
  subscribe(listener: (state: S & { [K in keyof C]: ReturnType<C[K]> }) => void): () => void;
}

function withComputed<S extends object, C extends Record<string, (state: S) => any>>(
  store: Store<S>,
  computed: C
): ComputedState<S, C> {
  const getComputedState = () => {
    const state = store.getState();
    const computedValues = Object.fromEntries(
      Object.entries(computed).map(([key, fn]) => [key, fn(state)])
    );
    return { ...state, ...computedValues };
  };

  return {
    getState: getComputedState as any,
    setState: store.setState.bind(store),
    subscribe(listener) {
      return store.subscribe(() => listener(getComputedState()));
    },
  };
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式