引言
在现代前端开发中,状态管理是一个核心问题。随着应用规模的扩大,全局状态管理变得越来越重要。Redux、Vuex、MobX和Zustand等库各有千秋,但它们都在尝试解决同一个问题:如何高效地管理和同步应用状态。今天,我们将从零开始,实现一个简洁而强大的状态管理库——StateFlow。
核心设计理念
StateFlow 基于以下核心理念:
- 单一数据源:所有状态存储在一个中央store中
- 不可变数据:状态修改通过创建新对象实现,保证状态追踪
- 发布订阅模式:组件订阅状态变化,实现精确更新
- 响应式API:简洁直观的接口,降低使用成本
我们将融合Redux的可预测性和Zustand的简洁API,同时借鉴MobX的响应式思想。
实现核心功能
首先,定义我们的基础API:
// stateflow.ts
type Listener = () => void;
type Selector<T, S> = (state: T) => S;
interface StateFlow<T> {
getState: () => T;
setState: (partial: Partial<T> | ((state: T) => Partial<T>)) => void;
subscribe: (listener: Listener) => () => void;
select: <S>(selector: Selector<T, S>) => StateFlow<S>;
}
function createStore<T extends object>(initialState: T): StateFlow<T> {
let state = { ...initialState };
const listeners = new Set<Listener>();
const getState = () => state;
const setState = (partial: Partial<T> | ((state: T) => Partial<T>)) => {
const nextPartial = typeof partial === 'function'
? partial(state)
: partial;
const nextState = { ...state, ...nextPartial };
if (!Object.is(nextState, state)) {
state = nextState;
listeners.forEach(listener => listener());
}
};
const subscribe = (listener: Listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const select = <S>(selector: Selector<T, S>): StateFlow<S> => {
let currentValue = selector(state);
return {
getState: () => selector(state),
setState: () => { throw new Error('Cannot set state of a selector'); },
subscribe: (listener: Listener) => {
const unsubscribe = subscribe(() => {
const nextValue = selector(state);
if (!Object.is(currentValue, nextValue)) {
currentValue = nextValue;
listener();
}
});
return unsubscribe;
},
select: <R>(nextSelector: Selector<S, R>) => {
const composedSelector = (s: T) => nextSelector(selector(s));
return select(composedSelector);
}
};
};
return { getState, setState, subscribe, select };
}
React集成
为了方便在React项目中使用,我们实现一个自定义Hook:
// react-stateflow.ts
import { useEffect, useState, useRef } from 'react';
import { StateFlow, Selector } from './stateflow';
export function useStore<T, S = T>(
store: StateFlow<T>,
selector?: Selector<T, S>
): S {
const selectedStore = selector ? store.select(selector) : store as unknown as StateFlow<S>;
const [state, setState] = useState<S>(() => selectedStore.getState());
// 使用useRef避免不必要的重新订阅
const storeRef = useRef(selectedStore);
useEffect(() => {
// 仅当store或selector变更时才更新storeRef
storeRef.current = selectedStore;
// 初始化时同步一次状态,避免闭包问题
setState(selectedStore.getState());
const unsubscribe = selectedStore.subscribe(() => {
setState(storeRef.current.getState());
});
return unsubscribe;
}, [selectedStore]);
return state;
}
高级功能:中间件机制
// middleware.ts
type NextFunction<T> = (partial: Partial<T> | ((state: T) => Partial<T>)) => void;
type MiddlewareAPI<T> = {
getState: () => T;
setState: NextFunction<T>;
};
type Middleware<T> = (
api: MiddlewareAPI<T>
) => (next: NextFunction<T>) => NextFunction<T>;
function applyMiddleware<T extends object>(
createStoreFunc: typeof createStore,
...middlewares: Middleware<T>[]
) {
return (initialState: T): StateFlow<T> => {
const store = createStoreFunc(initialState);
let dispatch = store.setState;
const middlewareAPI: MiddlewareAPI<T> = {
getState: store.getState,
setState: (action) => dispatch(action)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = chain.reduce(
(prev, current) => current(prev),
store.setState
);
return {
...store,
setState: dispatch
};
};
}
异步操作支持
// async.ts
type AsyncAction<T> = (
setState: (partial: Partial<T> | ((state: T) => Partial<T>)) => void,
getState: () => T
) => Promise<void> | void;
// 扩展store以支持异步操作
function enhanceStore<T extends object>(store: StateFlow<T>) {
const { setState, getState } = store;
const asyncDispatch = (action: AsyncAction<T>) => {
return action(setState, getState);
};
return {
...store,
asyncDispatch
};
}
// 使用示例
// asyncDispatch(async (setState, getState) => {
// setState({ loading: true });
// try {
// const data = await fetchData();
// setState({ data, loading: false });
// } catch (error) {
// setState({ error, loading: false });
// }
// });
时间旅行与DevTools集成
// devtools.ts
interface Action<T> {
type: string;
payload?: Partial<T> | ((state: T) => Partial<T>);
timestamp: number;
}
interface TimeTravel<T> {
getHistory: () => T[];
getActionHistory: () => Action<T>[];
goTo: (index: number) => void;
undo: () => void;
redo: () => void;
getCurrentIndex: () => number;
}
const devTools = <T extends object>(store: StateFlow<T>) => {
let actionIdx = -1;
const actionHistory: Action<T>[] = [];
const stateHistory: T[] = [store.getState()];
const originalSetState = store.setState;
const enhancedSetState = (partial: Partial<T> | ((state: T) => Partial<T>)) => {
const action: Action<T> = {
type: typeof partial === 'function' ? 'FUNCTION_ACTION' : 'UPDATE',
payload: partial,
timestamp: Date.now()
};
// 调用原始setState
originalSetState(partial);
// 截断历史记录,添加新状态
actionHistory.splice(actionIdx + 1);
stateHistory.splice(actionIdx + 1);
actionHistory.push(action);
stateHistory.push(store.getState());
actionIdx++;
};
// 时间旅行API
const timeTravel: TimeTravel<T> = {
getHistory: () => [...stateHistory],
getActionHistory: () => [...actionHistory],
getCurrentIndex: () => actionIdx,
goTo: (index: number) => {
if (index >= 0 && index < stateHistory.length) {
actionIdx = index;
originalSetState(() => stateHistory[index]);
}
},
undo: () => {
if (actionIdx > 0) {
timeTravel.goTo(actionIdx - 1);
}
},
redo: () => {
if (actionIdx < stateHistory.length - 1) {
timeTravel.goTo(actionIdx + 1);
}
}
};
return {
...store,
setState: enhancedSetState,
timeTravel
};
};
使用示例
// App.tsx
import React from 'react';
import { createStore } from './stateflow';
import { useStore } from './react-stateflow';
import { applyMiddleware } from './middleware';
import { devTools } from './devtools';
import { enhanceStore } from './async';
// 定义应用状态
interface AppState {
count: number;
todos: { id: number; text: string; completed: boolean }[];
theme: 'light' | 'dark';
loading: boolean;
error: Error | null;
}
// 创建初始状态
const initialState: AppState = {
count: 0,
todos: [],
theme: 'light',
loading: false,
error: null
};
// 创建日志中间件
const logger: Middleware<AppState> = store => next => action => {
console.log('before state:', store.getState());
console.log('action:', action);
const result = next(action);
console.log('after state:', store.getState());
return result;
};
// 创建性能监控中间件
const perfMonitor: Middleware<AppState> = store => next => action => {
const start = performance.now();
const result = next(action);
const end = performance.now();
console.log(`Action took ${end - start}ms to process`);
return result;
};
// 创建增强版store
const createStoreWithMiddleware = applyMiddleware(createStore, logger, perfMonitor);
const baseStore = createStoreWithMiddleware(initialState);
const store = enhanceStore(devTools(baseStore));
// 计数器组件
function Counter() {
const count = useStore(store, state => state.count);
return (
<div>
<h2>计数器: {count}</h2>
<button onClick={() => store.setState({ count: count + 1 })}>
增加
</button>
<button onClick={() => store.setState({ count: count - 1 })}>
减少
</button>
</div>
);
}
// Todo列表组件
function TodoList() {
const todos = useStore(store, state => state.todos);
const [text, setText] = React.useState('');
const addTodo = () => {
if (!text.trim()) return;
store.setState(state => ({
todos: [
...state.todos,
{
id: Date.now(),
text,
completed: false
}
]
}));
setText('');
};
const toggleTodo = (id: number) => {
store.setState(state => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
}));
};
// 异步加载示例
const loadSampleTodos = () => {
store.asyncDispatch(async (setState) => {
setState({ loading: true, error: null });
try {
// 模拟API请求
await new Promise(resolve => setTimeout(resolve, 1000));
setState(state => ({
todos: [
...state.todos,
{ id: Date.now(), text: '示例任务1', completed: false },
{ id: Date.now() + 1, text: '示例任务2', completed: true }
],
loading: false
}));
} catch (error) {
setState({ loading: false, error: error instanceof Error ? error : new Error('未知错误') });
}
});
};
// 获取加载状态
const loading = useStore(store, state => state.loading);
return (
<div>
<h2>待办事项</h2>
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
<input
value={text}
onChange={e => setText(e.target.value)}
placeholder="添加新任务"
disabled={loading}
/>
<button onClick={addTodo} disabled={loading}>添加</button>
<button onClick={loadSampleTodos} disabled={loading}>
{loading ? '加载中...' : '加载示例'}
</button>
</div>
{loading && <div>加载中...</div>}
<ul style={{ minHeight: '100px' }}>
{todos.map(todo => (
<li
key={todo.id}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
cursor: 'pointer',
padding: '5px 0'
}}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</li>
))}
{todos.length === 0 && !loading && (
<li style={{ color: '#999', fontStyle: 'italic' }}>暂无任务</li>
)}
</ul>
</div>
);
}
// 主题切换组件
function ThemeSwitcher() {
const theme = useStore(store, state => state.theme);
return (
<button
onClick={() =>
store.setState({ theme: theme === 'light' ? 'dark' : 'light' })
}
style={{
padding: '8px 16px',
borderRadius: '4px',
backgroundColor: theme === 'light' ? '#333' : '#fff',
color: theme === 'light' ? '#fff' : '#333',
border: 'none',
cursor: 'pointer'
}}
>
当前主题: {theme === 'light' ? '☀️' : '🌙'}
</button>
);
}
// 时间旅行控制器
function TimeTravel() {
const [history, setHistory] = React.useState(store.timeTravel.getHistory());
const currentIndex = store.timeTravel.getCurrentIndex();
// 监听状态变化,更新历史记录
React.useEffect(() => {
const unsubscribe = store.subscribe(() => {
setHistory([...store.timeTravel.getHistory()]);
});
return unsubscribe;
}, []);
return (
<div>
<h2>时间旅行</h2>
<div style={{ display: 'flex', gap: '10px', marginBottom: '10px' }}>
<button
onClick={() => store.timeTravel.undo()}
disabled={currentIndex <= 0}
>
⏮️ 撤销
</button>
<button
onClick={() => store.timeTravel.redo()}
disabled={currentIndex >= history.length - 1}
>
⏭️ 重做
</button>
</div>
<div>
<h3>历史状态: ({currentIndex + 1}/{history.length})</h3>
<ul style={{
height: '200px',
overflow: 'auto',
border: '1px solid #ccc',
borderRadius: '4px',
padding: '8px',
margin: '0'
}}>
{history.map((state, index) => (
<li
key={index}
style={{
padding: '4px 8px',
backgroundColor: index === currentIndex ? '#e6f7ff' : 'transparent',
borderRadius: '4px',
cursor: 'pointer',
marginBottom: '4px'
}}
>
<div onClick={() => store.timeTravel.goTo(index)}>
状态 #{index}
{index === currentIndex && ' (当前)'}
</div>
<pre style={{
fontSize: '12px',
overflow: 'hidden',
maxHeight: index === currentIndex ? 'none' : '60px'
}}>
{JSON.stringify(state, null, 2)}
</pre>
</li>
))}
</ul>
</div>
</div>
);
}
// 可视化状态图表组件
function StateVisualizer() {
const count = useStore(store, state => state.count);
const todos = useStore(store, state => state.todos);
const completedTodos = todos.filter(todo => todo.completed).length;
const pendingTodos = todos.length - completedTodos;
return (
<div>
<h2>状态可视化</h2>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '10px',
marginTop: '10px'
}}>
<div>
<div>计数器值: {count}</div>
<div style={{
height: '20px',
width: `${Math.min(Math.abs(count) * 10, 100)}%`,
backgroundColor: count >= 0 ? '#52c41a' : '#f5222d',
borderRadius: '4px',
transition: 'all 0.3s ease'
}} />
</div>
<div>
<div>待办项: {todos.length} (已完成: {completedTodos}, 待完成: {pendingTodos})</div>
{todos.length > 0 && (
<div style={{ display: 'flex', height: '20px', borderRadius: '4px', overflow: 'hidden' }}>
<div style={{
flex: completedTodos,
backgroundColor: '#52c41a',
transition: 'all 0.3s ease'
}} />
<div style={{
flex: pendingTodos,
backgroundColor: '#faad14',
transition: 'all 0.3s ease'
}} />
</div>
)}
</div>
</div>
</div>
);
}
// 主应用
function App() {
const theme = useStore(store, state => state.theme);
const error = useStore(store, state => state.error);
return (
<div style={{
padding: '20px',
backgroundColor: theme === 'light' ? '#fff' : '#222',
color: theme === 'light' ? '#222' : '#fff',
minHeight: '100vh',
transition: 'all 0.3s ease'
}}>
<h1>StateFlow 演示</h1>
<ThemeSwitcher />
<hr />
{error && (
<div style={{
padding: '10px',
backgroundColor: '#fff2f0',
borderRadius: '4px',
marginBottom: '20px',
color: '#f5222d',
border: '1px solid #ffccc7'
}}>
错误: {error.message}
</div>
)}
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
gap: '20px'
}}>
<div>
<Counter />
<TodoList />
<StateVisualizer />
</div>
<TimeTravel />
</div>
</div>
);
}
export default App;
性能优化
StateFlow 通过以下机制实现性能优化:
- 选择器记忆化:避免不必要的重新计算和渲染
- 浅比较:使用 Object.is 进行状态比较,减少不必要的更新
- 批量更新:结合 React 的批处理机制
- 选择性渲染:只有当关心的部分状态变化时才触发更新
可以进一步增强:
// optimized-selectors.ts
function createSelector<T, R>(
selectors: ((state: T) => any)[],
combiner: (...args: any[]) => R,
equalityFn: (a: R, b: R) => boolean = Object.is
): (state: T) => R {
let lastInputs: any[] | undefined;
let lastResult: R | undefined;
return (state: T) => {
const inputs = selectors.map(selector => selector(state));
if (
!lastInputs ||
inputs.length !== lastInputs.length ||
inputs.some((input, i) => !Object.is(input, lastInputs![i]))
) {
const newResult = combiner(...inputs);
// 允许自定义相等检查函数
if (!lastResult || !equalityFn(lastResult, newResult)) {
lastResult = newResult;
}
}
lastInputs = inputs;
return lastResult as R;
};
}
// 批量处理状态更新的工具函数
function batch<T>(store: StateFlow<T>, fn: () => void): void {
const originalSetState = store.setState;
let batching = false;
let pendingState: Partial<T> = {};
store.setState = (partial) => {
if (typeof partial === 'function') {
// 对于函数类型,立即执行以获取实际更新
const partialState = partial(store.getState());
pendingState = { ...pendingState, ...partialState };
} else {
pendingState = { ...pendingState, ...partial };
}
};
try {
batching = true;
fn();
} finally {
batching = false;
store.setState = originalSetState;
if (Object.keys(pendingState).length > 0) {
originalSetState(pendingState);
}
}
}
持久化方案
// persistence.ts
type StorageEngine = {
getItem: (key: string) => string | null | Promise<string | null>;
setItem: (key: string, value: string) => void | Promise<void>;
};
interface PersistOptions<T> {
key: string;
storage?: StorageEngine;
whitelist?: (keyof T)[];
blacklist?: (keyof T)[];
serialize?: (data: any) => string;
deserialize?: (data: string) => any;
throttle?: number;
}
async function persistStore<T>(
store: StateFlow<T>,
options: PersistOptions<T>
) {
const {
key,
storage = localStorage,
whitelist,
blacklist,
serialize = JSON.stringify,
deserialize = JSON.parse,
throttle = 0
} = options;
// 过滤状态,只保留需要持久化的部分
const filterState = (state: T): Partial<T> => {
if (whitelist) {
return Object.fromEntries(
whitelist.map(k => [k, state[k]])
) as Partial<T>;
} else if (blacklist) {
const result = { ...state };
blacklist.forEach(k => delete result[k as keyof T]);
return result;
}
return state;
};
// 初始化:从存储中恢复状态
try {
const savedState = await Promise.resolve(storage.getItem(key));
if (savedState) {
const parsedState = deserialize(savedState);
store.setState(parsedState);
}
} catch (e) {
console.error('Failed to restore state from storage:', e);
}
// 节流函数
let timeout: number | null = null;
const saveState = () => {
try {
const state = filterState(store.getState());
const serializedState = serialize(state);
Promise.resolve(storage.setItem(key, serializedState));
} catch (e) {
console.error('Failed to persist state to storage:', e);
}
};
// 订阅变更:将状态保存到存储
store.subscribe(() => {
if (throttle === 0) {
saveState();
} else if (timeout === null) {
timeout = window.setTimeout(() => {
saveState();
timeout = null;
}, throttle);
}
});
// 添加手动持久化方法
return {
...store,
persist: saveState
};
}
与React Concurrent Mode的协作
// concurrent.ts
import { startTransition, useTransition } from 'react';
function concurrentStore<T extends object>(initialState: T) {
const store = createStore(initialState);
const originalSetState = store.setState;
store.setState = (partial) => {
startTransition(() => {
originalSetState(partial);
});
};
return store;
}
// 与React 18的useTransition结合使用的Hook
export function useTransitionStore<T, S = T>(
store: StateFlow<T>,
selector?: Selector<T, S>
): [S, boolean, (updater: Partial<T> | ((state: T) => Partial<T>)) => void] {
const [isPending, startStateTransition] = useTransition();
const state = useStore(store, selector);
const setTransitionState = (updater: Partial<T> | ((state: T) => Partial<T>)) => {
startStateTransition(() => {
store.setState(updater);
});
};
return [state, isPending, setTransitionState];
}
不同设计模式对比
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Flux/Redux | 单向数据流,可预测性强,便于调试 | 样板代码多,初学者难以掌握 | 大型应用,多人协作,需要高可追踪性 |
| MobX | 响应式编程,代码简洁,自动追踪依赖 | 黑盒机制,调试复杂,可能过度优化 | 中小型应用,追求开发效率 |
| Zustand | API简洁,无需Context Provider,模块化 | 缺少内置中间件,生态相对较小 | 轻量级应用,React hooks用户 |
| Recoil | 原子化管理,良好并发支持,细粒度控制 | API复杂,需要RecoilRoot包装 | 需要细粒度控制的复杂UI应用 |
| Jotai | 超轻量,原子化设计,组合性强 | 复杂场景需要额外抽象 | 原子化状态管理,轻量级应用 |
| StateFlow | 简洁API,灵活中间件,高性能选择器 | 需要自行处理一些边界情况 | 希望兼顾简洁与灵活的应用 |
StateFlow 结合了Flux的可预测性和Zustand的简洁性,同时通过选择器机制和中间件提供了极大的灵活性。
调试工具可视化
// devtools-visualizer.ts
// 可以与主流浏览器开发者工具集成的可视化调试工具
class StateFlowDevTools<T> {
private store: StateFlow<T>;
private connected: boolean = false;
private actionLog: {
type: string;
payload: any;
timestamp: number;
stateBefore: T;
stateAfter: T;
}[] = [];
constructor(store: StateFlow<T>) {
this.store = store;
this.init();
}
private init() {
// 拦截setState并记录变化
const originalSetState = this.store.setState;
this.store.setState = (partial) => {
const stateBefore = this.store.getState();
originalSetState(partial);
const stateAfter = this.store.getState();
const actionType = typeof partial === 'function'
? 'FUNCTION_ACTION'
: 'UPDATE';
const entry = {
type: actionType,
payload: typeof partial === 'function' ? 'Function' : partial,
timestamp: Date.now(),
stateBefore,
stateAfter
};
this.actionLog.push(entry);
// 发送到开发者工具
if (this.connected && typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
window.__REDUX_DEVTOOLS_EXTENSION__.send(
{ type: actionType },
stateAfter
);
}
};
// 连接到Redux DevTools
if (typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__) {
const devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({
name: 'StateFlow Store'
});
devTools.init(this.store.getState());
devTools.subscribe((message) => {
if (message.type === 'DISPATCH' && message.payload.type === 'JUMP_TO_ACTION') {
const index = parseInt(message.payload.actionId, 10);
if (this.actionLog[index]) {
originalSetState(() => this.actionLog[index].stateAfter);
}
}
});
this.connected = true;
}
}
// 公开API
getActionLog() {
return [...this.actionLog];
}
jumpToAction(index: number) {
if (this.actionLog[index]) {
this.store.setState(() => this.actionLog[index].stateAfter);
}
}
}
// 扩展window类型
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION__?: any;
}
}
// 创建DevTools
const connectDevTools = <T>(store: StateFlow<T>) => {
const devTools = new StateFlowDevTools(store);
return {
...store,
devTools
};
};
总结
我们从零设计并实现了StateFlow,一个功能全面且高性能的状态管理库。它具有:
- 核心API简洁易用:仅提供getState、setState、subscribe和select四个核心方法
- 高效的状态更新机制:使用不可变更新和浅比较优化性能
- 灵活的中间件系统:支持日志、异步操作、性能监控等扩展功能
- 精确的选择器系统:支持组合选择器和记忆化,避免不必要的重渲染
- 内置的时间旅行功能:方便调试和理解状态变化过程
- 可选的持久化方案:支持本地存储、会话存储或自定义存储引擎
- 与React Concurrent Mode协作:利用React 18的新特性优化用户体验
- 完善的调试工具:可视化状态变化,支持时间旅行调试
通过这次"造轮子"实践,我们不仅深入理解了状态管理的核心概念和不同设计模式的优劣,也学习了如何平衡API简洁性和功能丰富性。这个项目虽然是个"玩具级"实现,但包含了现代状态管理库的大部分核心理念和技术,希望对你理解前端状态管理有所帮助!