从零实现一个轻量级 React 状态管理库:设计思路与最佳实践

4 阅读3分钟

引言

在现代前端开发中,状态管理一直是构建复杂应用的核心挑战。随着 React Hooks 的普及,开发者有了更多轻量级的状态管理选择。本文将带你从零实现一个轻量级的 React 状态管理库,深入探讨其设计思路、实现细节和最佳实践。

为什么需要另一个状态管理库?

Redux、MobX、Zustand 等成熟方案已经非常优秀,但在某些场景下,我们可能希望:

  1. 更小的包体积(< 2KB)
  2. 更简单的 API 设计
  3. 更好的 TypeScript 支持
  4. 更灵活的状态更新策略

让我们开始构建一个名为 MicroState 的轻量级状态管理库。

核心设计目标

我们的状态管理库将遵循以下设计原则:

  • 极简 API:只需 3 个核心函数
  • 零依赖:不依赖任何第三方库
  • 完整的 TypeScript 支持
  • 支持中间件扩展
  • 高性能更新:避免不必要的重渲染

基础架构设计

1. 状态存储设计

首先,我们需要一个中心化的状态存储:

type Listener<T> = (state: T) => void;
type Unsubscribe = () => void;

class Store<T> {
  private state: T;
  private listeners: Set<Listener<T>> = new Set();

  constructor(initialState: T) {
    this.state = initialState;
  }

  getState(): T {
    return this.state;
  }

  setState(updater: T | ((prevState: T) => T)) {
    const nextState = typeof updater === 'function'
      ? (updater as (prev: T) => T)(this.state)
      : updater;
    
    if (nextState !== this.state) {
      this.state = nextState;
      this.notifyListeners();
    }
  }

  subscribe(listener: Listener<T>): Unsubscribe {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  private notifyListeners() {
    this.listeners.forEach(listener => listener(this.state));
  }
}

2. React 集成 Hook

为了让状态与 React 组件无缝集成,我们需要创建一个自定义 Hook:

import { useState, useEffect, useRef } from 'react';

export function createUseStore<T>(store: Store<T>) {
  return function useStore<U = T>(
    selector?: (state: T) => U
  ): U {
    const [state, setState] = useState(() => 
      selector ? selector(store.getState()) : store.getState()
    );
    
    const selectorRef = useRef(selector);
    const stateRef = useRef(state);

    useEffect(() => {
      selectorRef.current = selector;
      stateRef.current = state;
    });

    useEffect(() => {
      const checkForUpdates = () => {
        const nextState = selectorRef.current
          ? selectorRef.current(store.getState())
          : store.getState();
        
        if (nextState !== stateRef.current) {
          setState(nextState);
        }
      };

      const unsubscribe = store.subscribe(checkForUpdates);
      return unsubscribe;
    }, [store]);

    return state;
  };
}

3. 创建 Store 的工厂函数

export function createStore<T>(initialState: T) {
  const store = new Store(initialState);
  
  return {
    getState: store.getState.bind(store),
    setState: store.setState.bind(store),
    subscribe: store.subscribe.bind(store),
    useStore: createUseStore(store),
  };
}

完整实现与类型安全

让我们将所有部分组合起来,并添加完整的 TypeScript 支持:

// micro-state.ts
export type StoreApi<T> = {
  getState: () => T;
  setState: (updater: T | ((prevState: T) => T)) => void;
  subscribe: (listener: (state: T) => void) => () => void;
  useStore: <U = T>(selector?: (state: T) => U) => U;
};

export function createStore<T>(initialState: T): StoreApi<T> {
  type Listener = (state: T) => void;
  
  let state = initialState;
  const listeners = new Set<Listener>();

  const getState = () => state;

  const setState = (updater: T | ((prevState: T) => T)) => {
    const nextState = typeof updater === 'function'
      ? (updater as (prev: T) => T)(state)
      : updater;
    
    if (!Object.is(nextState, state)) {
      state = nextState;
      listeners.forEach(listener => listener(state));
    }
  };

  const subscribe = (listener: Listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };

  const useStore = <U = T>(selector?: (state: T) => U): U => {
    const [selectedState, setSelectedState] = React.useState(() =>
      selector ? selector(state) : state
    );

    React.useEffect(() => {
      const listener = () => {
        const nextSelectedState = selector ? selector(state) : state;
        if (!Object.is(nextSelectedState, selectedState)) {
          setSelectedState(nextSelectedState);
        }
      };
      
      const unsubscribe = subscribe(listener);
      return unsubscribe;
    }, [selector]);

    return selectedState as U;
  };

  return {
    getState,
    setState,
    subscribe,
    useStore,
  };
}

中间件系统

为了提供扩展能力,我们实现一个简单的中间件系统:

type Middleware<T> = (
  store: StoreApi<T>
) => (next: (updater: T | ((prev: T) => T)) => void) => 
  (updater: T | ((prev: T) => T)) => void;

export function applyMiddleware<T>(
  store: StoreApi<T>,
  middlewares: Middleware<T>[]
): StoreApi<T> {
  let setState = store.setState;
  
  // 从右到左组合中间件
  const chain = middlewares.map(middleware => middleware(store));
  
  chain.reverse().forEach(middleware => {
    setState = middleware(setState);
  });

  return {
    ...store,
    setState,
  };
}

// 示例:日志中间件
export const loggerMiddleware: Middleware<any> = (store) => (next) => (updater) => {
  console.group('State Update');
  console.log('Previous State:', store.getState());
  
  const result = next(updater);
  
  console.log('Next State:', store.getState());
  console.groupEnd();
  
  return result;
};

// 示例:持久化中间件
export const persistMiddleware = <T>(key: string): Middleware<T> => (store) => (next) => (updater) => {
  const result = next(updater);
  
  // 保存到 localStorage
  try {
    localStorage.setItem(key, JSON.stringify(store.getState()));
  } catch (error) {
    console.warn('Failed to persist state:', error);
  }
  
  return result;
};

使用示例

让我们看看如何在真实项目中使用这个状态管理库:

// store/counterStore.ts
import { createStore, applyMiddleware, loggerMiddleware } from '../micro-state';

interface CounterState {
  count: number;
  loading: boolean;
}

const initialState: CounterState = {
  count: 0,
  loading: false,
};

// 创建基础 store
const baseStore = createStore(initialState);

// 应用中间件
export const counterStore = applyMiddleware(
  baseStore,
  [loggerMiddleware]
);

// 定义 actions
export const counterActions = {
  increment: () => {
    counterStore.setState(prev => ({
      ...prev,
      count: prev.count + 1,
    }));
  },
  
  decrement: () => {
    counterStore.setState(prev => ({
      ...prev,
      count: prev.count - 1,
    }));
  },
  
  incrementAsync: async () => {
    counterStore.setState(prev => ({ ...prev, loading: true }));
    
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    counterStore.setState(prev => ({
      ...prev,
      count: prev.count + 1,
      loading: false,
    }));
  },
};

在组件中使用:

// components/Counter.tsx
import React from 'react';
import { counterStore, counterActions } from '../store/counterStore';

export const Counter: React.FC = () => {
  // 使用完整状态
  const { count, loading } = counterStore.useStore();
  
  // 或者使用选择器获取部分状态
  const countOnly = counterStore.useStore(state => state.count);
  
  return (
    <div className="counter">
      <h2>Count: {count}</h2>
      <div className="buttons">
        <button 
          onClick