前言
在 React 应用开发中,状态管理一直是开发者面临的核心挑战之一。随着应用规模的增长,组件间的状态共享和同步变得日益复杂。虽然 Redux、MobX、Zustand 等成熟方案提供了完善的解决方案,但有时候我们需要的只是一个轻量级、易理解、无依赖的状态管理工具。
本文将带你从零实现一个轻量级的 React 状态管理库,我们将称之为 MiniStore。通过这个实践,你不仅能掌握状态管理的核心原理,还能深入理解 React Hooks 的工作机制。
设计目标
在开始编码之前,我们先明确 MiniStore 的设计目标:
- 极简 API:提供最简单直观的 API 设计
- TypeScript 支持:完整的类型推断
- 高性能:最小化不必要的重渲染
- 零依赖:不依赖任何第三方库
- React 18 兼容:支持并发特性
核心实现
1. 创建 Store 核心
首先,我们实现 Store 的核心逻辑。Store 需要能够存储状态、管理订阅者、通知更新:
// types.ts
export type Listener<T> = (state: T) => void;
export type SetStateAction<T> = T | ((prevState: T) => T);
export type Selector<T, R> = (state: T) => R;
// store.ts
export 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: SetStateAction<T>): void {
const nextState = typeof updater === 'function'
? (updater as (prev: T) => T)(this.state)
: updater;
if (nextState !== this.state) {
this.state = nextState;
this.notify();
}
}
subscribe(listener: Listener<T>): () => void {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
private notify(): void {
this.listeners.forEach(listener => {
listener(this.state);
});
}
}
2. 实现 React Hooks 集成
接下来,我们创建 React Hooks 来连接 Store 和组件:
// hooks.ts
import { useEffect, useSyncExternalStore, useMemo } from 'react';
import { Store, Selector } from './store';
// 创建自定义 Hook 来使用 Store
export function useStore<T>(store: Store<T>): T {
const state = useSyncExternalStore(
store.subscribe.bind(store),
store.getState.bind(store)
);
return state;
}
// 支持选择器的 Hook,用于性能优化
export function useStoreSelector<T, R>(
store: Store<T>,
selector: Selector<T, R>
): R {
const getSnapshot = useMemo(() => {
let lastSnapshot: R | null = null;
let lastState: T | null = null;
return () => {
const currentState = store.getState();
// 如果状态没有变化,返回缓存的快照
if (currentState === lastState && lastSnapshot !== null) {
return lastSnapshot;
}
lastState = currentState;
lastSnapshot = selector(currentState);
return lastSnapshot;
};
}, [store, selector]);
const subscribe = useMemo(() => {
return store.subscribe.bind(store);
}, [store]);
return useSyncExternalStore(subscribe, getSnapshot);
}
// 创建 Store 的 Hook
export function createStoreHook<T>(store: Store<T>) {
return () => useStore(store);
}
export function createStoreSelectorHook<T>(store: Store<T>) {
return <R>(selector: Selector<T, R>) =>
useStoreSelector(store, selector);
}
3. 实现 Provider 模式
为了支持多个 Store 和更好的依赖注入,我们实现 Provider 模式:
// context.tsx
import React, { createContext, useContext, useMemo } from 'react';
import { Store } from './store';
// 创建 Store Context
export function createStoreContext<T>(initialState: T) {
const StoreContext = createContext<Store<T> | null>(null);
const StoreProvider: React.FC<{
children: React.ReactNode;
initialState?: T;
}> = ({ children, initialState: providerInitialState }) => {
const store = useMemo(() => {
return new Store(providerInitialState || initialState);
}, []);
return (
<StoreContext.Provider value={store}>
{children}
</StoreContext.Provider>
);
};
function useStoreContext() {
const store = useContext(StoreContext);
if (!store) {
throw new Error('useStoreContext must be used within StoreProvider');
}
return store;
}
return { StoreProvider, useStoreContext };
}
4. 实现 Action 和 Reducer 模式
为了提供更结构化的状态更新方式,我们实现 Action 和 Reducer 支持:
// reducer.ts
export type Action = {
type: string;
payload?: any;
};
export type Reducer<T> = (state: T, action: Action) => T;
export class ReducerStore<T> extends Store<T> {
private reducer: Reducer<T>;
constructor(initialState: T, reducer: Reducer<T>) {
super(initialState);
this.reducer = reducer;
}
dispatch(action: Action): void {
const nextState = this.reducer(this.getState(), action);
this.setState(nextState);
}
}
// 创建 Reducer Store 的 Hook
export function useReducerStore<T>(
reducer: Reducer<T>,
initialState: T
): [T, (action: Action) => void] {
const store = useMemo(() => {
return new ReducerStore(initialState, reducer);
}, [reducer, initialState]);
const state = useStore(store);
const dispatch = useCallback(
(action: Action) => store.dispatch(action),
[store]
);
return [state, dispatch];
}
完整示例:Todo 应用
让我们通过一个完整的 Todo 应用来演示如何使用我们的 MiniStore:
// todoStore.ts
import { createStoreContext } from './context';
export interface Todo {
id: string;
text: string;
completed: boolean;
}
export interface TodoState {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
}
const initialState: TodoState = {
todos: [],
filter: 'all',
};
export const { StoreProvider: TodoProvider, useStoreContext } =
createStoreContext(initialState);
// 自定义 Hook 封装业务逻辑
export function useTodoStore() {
const store = useStoreContext();
const addTodo = useCallback((text: string) => {
store.setState(prev => ({
...prev,
todos: [
...prev.todos,
{
id: Date.now().toString(),
text,
completed: false,
},
],
}));
}, [store]);
const toggleTodo = useCallback((id: string) => {
store.setState(prev => ({
...prev,
todos: prev.todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
),
}));
}, [store]);
const setFilter = useCallback((filter: TodoState['filter']) => {
store.setState(prev => ({
...prev,
filter,
}));
}, [store]);
const filteredTodos = useStoreSelector(store, state => {
switch (state.filter) {
case 'active':
return state.todos.filter(todo => !todo.completed);
case 'completed':
return state.todos.filter(todo => todo.completed);
default:
return state.todos;
}
});
return {
todos: filteredTodos,
filter: useStoreSelector(store, state => state.filter),
addTodo,
toggleTodo,
setFilter,
};
}
// TodoApp.tsx
import React from 'react';
import { TodoProvider, useTodoStore } from './todoStore';
const TodoList: React.FC = () => {
const { todos, toggleTodo } = useTodoStore();
return (
<ul>
{todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
cursor: 'pointer',
}}
>
{todo.text}
</li>
))}
</ul>
);
};
const AddTodo: React.FC = () => {
const [text, setText] = React.useState('');
const {