单向数据流与状态管理
单向数据流是现代前端架构的核心思想,配合状态管理可构建可预测、易调试的应用。
单向数据流核心概念
数据流向
JavaScript
Action → Dispatcher → Store → View → Action
单向数据流特点:
- 单向传递:数据只能从Store流向View
- 状态不可变:Store是唯一数据源
- 可预测性:状态变化由Action触发
与双向绑定对比
| 特性 | 单向数据流 | 双向绑定 |
|---|---|---|
| 数据流向 | 单向循环 | 双向同步 |
| 状态来源 | 唯一Store | 分散各处 |
| 调试难度 | 低(可追溯) | 高(难以追踪) |
| 学习曲线 | 较陡 | 平缓 |
| 代表框架 | Redux、Vuex、Zustand | Vue(v-model)、Angular |
Flux架构
核心组件
- Action:描述状态变化意图
- Dispatcher:分发Action到Store
- Store:存储状态,处理Action
- View:响应Store变化
Flux实现
JavaScript
// Dispatcher
class Dispatcher {
constructor() {
this.stores = [];
}
register(store) {
this.stores.push(store);
return this.stores.length - 1;
}
dispatch(action) {
this.stores.forEach(store => store.handleAction(action));
}
}
const dispatcher = new Dispatcher();
// Action Creator
const Actions = {
addTodo(text) {
dispatcher.dispatch({
type: 'ADD_TODO',
payload: { text }
});
},
toggleTodo(id) {
dispatcher.dispatch({
type: 'TOGGLE_TODO',
payload: { id }
});
}
};
// Store
class TodoStore {
constructor() {
this.todos = [];
this.listeners = [];
dispatcher.register(this);
}
handleAction(action) {
switch (action.type) {
case 'ADD_TODO':
this.todos.push({
id: Date.now(),
text: action.payload.text,
completed: false
});
this.emit();
break;
case 'TOGGLE_TODO':
const todo = this.todos.find(t => t.id === action.payload.id);
if (todo) {
todo.completed = !todo.completed;
this.emit();
}
break;
}
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
emit() {
this.listeners.forEach(l => l(this.todos));
}
getState() {
return this.todos;
}
}
const todoStore = new TodoStore();
// View
todoStore.subscribe(todos => {
render(todos);
});
// 触发更新
Actions.addTodo('Learn Flux');
Redux模式
三大原则
- 单一数据源:整个应用状态存储在一个Store
- 状态只读:只能通过dispatch Action修改
- 纯函数修改:Reducer必须是纯函数
Redux实现
JavaScript
// 创建Store
function createStore(reducer, initialState) {
let state = initialState;
const listeners = [];
return {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
},
subscribe(listener) {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
};
}
// Reducer(纯函数)
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.payload.id,
text: action.payload.text,
completed: false
}
];
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
);
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.payload.id);
default:
return state;
}
}
// 合并Reducer
function combineReducers(reducers) {
return (state = {}, action) => {
const nextState = {};
Object.keys(reducers).forEach(key => {
nextState[key] = reducers[key](state[key], action);
});
return nextState;
};
}
// 使用
const rootReducer = combineReducers({
todos: todoReducer,
filter: filterReducer
});
const store = createStore(rootReducer);
// 订阅变化
store.subscribe(() => {
console.log('State:', store.getState());
});
// 分发Action
store.dispatch({
type: 'ADD_TODO',
payload: { id: 1, text: 'Learn Redux' }
});
Redux中间件
JavaScript
// 中间件结构
const logger = store => next => action => {
console.log('Dispatching:', action);
const result = next(action);
console.log('Next state:', store.getState());
return result;
};
// 异步中间件
const thunk = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
};
// 应用中间件
function applyMiddleware(store, middlewares) {
let dispatch = store.dispatch;
middlewares.reverse().forEach(middleware => {
dispatch = middleware(store)(dispatch);
});
return { ...store, dispatch };
}
// 使用
const store = createStore(rootReducer);
applyMiddleware(store, [logger, thunk]);
// 异步Action
const fetchTodos = () => async (dispatch) => {
dispatch({ type: 'FETCH_TODOS_START' });
try {
const todos = await api.getTodos();
dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
} catch (error) {
dispatch({ type: 'FETCH_TODOS_ERROR', payload: error });
}
};
store.dispatch(fetchTodos());
现代状态管理
Zustand(轻量级)
JavaScript
import { create } from 'zustand';
const useStore = create((set, get) => ({
todos: [],
addTodo: (text) => set(state => ({
todos: [...state.todos, { id: Date.now(), text, completed: false }]
})),
toggleTodo: (id) => set(state => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
getCompletedCount: () => {
return get().todos.filter(todo => todo.completed).length;
}
}));
// 组件中使用
function TodoList() {
const todos = useStore(state => state.todos);
const addTodo = useStore(state => state.addTodo);
return (
<div>
{todos.map(todo => <div key={todo.id}>{todo.text}</div>)}
<button onClick={() => addTodo('New todo')}>Add</button>
</div>
);
}
Valtio(响应式代理)
JavaScript
import { proxy, useSnapshot } from 'valtio';
const state = proxy({
count: 0,
todos: []
});
// 修改状态(直接修改)
state.count += 1;
state.todos.push({ text: 'New todo', completed: false });
// React组件
function Counter() {
const snap = useSnapshot(state);
return (
<div>
<p>{snap.count}</p>
<button onClick={() => state.count++}>Increment</button>
</div>
);
}
Jotai(原子化)
JavaScript
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const doubleCountAtom = atom(get => get(countAtom) * 2);
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubleCount] = useAtom(doubleCountAtom);
return (
<div>
<p>Count: {count}</p>
<p>Double: {doubleCount}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
// 派生原子
const todosAtom = atom([]);
const completedTodosAtom = atom(get =>
get(todosAtom).filter(t => t.completed)
);
// 写入派生原子
const addTodoAtom = atom(null, (get, set, text) => {
set(todosAtom, [...get(todosAtom), { text, completed: false }]);
});
状态管理最佳实践
状态分类
JavaScript
// UI状态:组件内部
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState('');
// 应用状态:全局共享
const store = useStore();
// 服务端状态:使用专门库
import { useQuery } from '@tanstack/react-query';
function UserProfile() {
const { data, isLoading } = useQuery(['user', userId], fetchUser);
}
状态结构设计
JavaScript
// ❌ 扁平状态
const state = {
users: {
1: { id: 1, name: 'Alice' },
2: { id: 2, name: 'Bob' }
},
userOrder: [1, 2]
};
// ❌ 嵌套过深
const state = {
app: {
data: {
users: {
items: {
// ...
}
}
}
}
};
// ✅ 扁平化 + 规范化
const state = {
users: {
byId: {
1: { id: 1, name: 'Alice' },
2: { id: 2, name: 'Bob' }
},
allIds: [1, 2]
}
};
Action命名规范
JavaScript
// 资源 + 操作 + 状态
const ActionTypes = {
// 异步操作
TODOS_FETCH_START: 'TODOS_FETCH_START',
TODOS_FETCH_SUCCESS: 'TODOS_FETCH_SUCCESS',
TODOS_FETCH_ERROR: 'TODOS_FETCH_ERROR',
// 同步操作
TODO_ADD: 'TODO_ADD',
TODO_UPDATE: 'TODO_UPDATE',
TODO_REMOVE: 'TODO_REMOVE',
// 批量操作
TODOS_BATCH_UPDATE: 'TODOS_BATCH_UPDATE'
};
状态持久化
text
// 本地存储持久化
const persistStore = (key, store) => {
const saved = localStorage.getItem(key);
if (saved) {
store.setState(JSON.parse(saved));
}
store.subscribe(() => {
localStorage.setItem(key, JSON.stringify(store.getState()));
});
};
// 使用
persistStore('app-state', store);
要点总结
- 单向数据流:Action → Store → View,可预测性强
- Flux架构:Dispatcher分发,Store处理,View响应
- Redux原则:单一Store、状态只读、纯函数Reducer
- 中间件机制:处理异步、日志、错误等横切关注点
- 现代选择:Zustand轻量、Valtio响应式、Jotai原子化
- 状态分类:UI状态本地化、应用状态全局化、服务端状态专用库
存放路径:articles/JS/专家/设计模式与架构思想/单向数据流与状态管理.md
📝 发现内容有误?点击此处直接编辑