1 Redux 概述与核心概念
Redux 是一个用于 JavaScript 应用程序的可预测状态容器,它以集中式 Store 的方式对整个应用中使用的状态进行集中管理,其规则确保状态只能以可预测的方式更新。虽然常与 React 搭配使用,但 Redux 本身是一个独立的库,可以与其他视图库一起使用。
1.1 Redux 三大设计原则
- 单一数据源 :整个应用的 state 被存储在单个 store 的对象树中。这使得应用程序的状态更加 predictable 且易于调试。
- State 是只读的 :改变 state 的唯一方法 是触发 action(一个描述发生了什么的对象)。这确保了视图和网络请求都不能直接修改 state。
- 使用纯函数进行更改 :为了指定 state 如何被 action 转换,你需要编写 reducers(纯函数),它接收先前的 state 和 action,并返回新的 state。
1.2 核心术语
术语 | 描述 | 示例 |
---|---|---|
Action | 具有 type 字段的普通对象,描述应用中发生的事件 |
{ type: 'INCREMENT', payload: 2 } |
Action Creator | 创建并返回 action 对象的函数 | const increment = (amount) => ({ type: 'INCREMENT', payload: amount }) |
Reducer | 纯函数,接收当前 state 和 action,返回新 state | (state, action) => newState |
Store | 集中管理状态的对象 | 由 createStore(reducer) 创建 |
Dispatch | Store 的方法,用于更新 state 的唯一方式,接收一个 action 对象 | store.dispatch(action) |
Selector | 函数,从 store 状态树中提取指定的片段 | const selectCounter = state => state.value |
2 Redux 源码结构与核心实现
Redux 源码非常简洁(约 2kB),主要集中在 src
目录下的几个文件:
bash
src
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
└── index.js # 入口文件
2.1 createStore
源码解析
createStore
是 Redux 的核心,用于创建一个状态容器 store。
javascript
// 简化后的 createStore 核心结构
export default function createStore(reducer, preloadedState, enhancer) {
// 参数处理:支持省略 preloadedState
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
// 如果传入了 enhancer(如 applyMiddleware),则用 enhancer 增强 createStore
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
// 注意:这里返回的是增强后的 store,不再继续执行下面的逻辑
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
let currentReducer = reducer; // 当前 reducer 函数
let currentState = preloadedState; // 当前 state
let currentListeners = []; // 当前监听器列表
let nextListeners = currentListeners;
let isDispatching = false; // 是否正在 dispatch 的标记
// 确保 nextListeners 是可修改的副本
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice();
}
}
// 获取当前 state
function getState() {
if (isDispatching) {
throw new Error('You may not call store.getState() while the reducer is executing...');
}
return currentState;
}
// 订阅 state 变化
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.');
}
if (isDispatching) {
throw new Error('You may not call store.subscribe() while the reducer is executing...');
}
let isSubscribed = true;
ensureCanMutateNextListeners();
nextListeners.push(listener); // 将监听器加入列表
// 返回取消订阅的函数
return function unsubscribe() {
if (!isSubscribed) return;
if (isDispatching) {
throw new Error('You may not unsubscribe from a store listener while the reducer is executing...');
}
isSubscribed = false;
ensureCanMutateNextListeners();
const index = nextListeners.indexOf(listener);
nextListeners.splice(index, 1); // 从监听器列表中移除
};
}
// 分发 action,触发 state 更新的唯一方式
function dispatch(action) {
if (!isPlainObject(action)) { // 检查 action 是否为普通对象
throw new Error('Actions must be plain objects. Use custom middleware for async actions.');
}
if (typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. Have you misspelled a constant?');
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
// 关键:将当前 state 和 action 传给 reducer,计算新的 state
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
// 通知所有订阅者 state 已更改
const listeners = (currentListeners = nextListeners);
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
listener(); // 调用监听器(通常用于触发视图更新)
}
return action; // 返回 action
}
// 替换当前 reducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.');
}
currentReducer = nextReducer;
dispatch({ type: ActionTypes.INIT }); // 触发 INIT action 初始化新 state
}
// 初始化:分发一个初始 action 来设置初始 state
dispatch({ type: ActionTypes.INIT });
// 返回 store 的公共 API
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable // 支持 Observable 的符号
};
}
2.2 combineReducers
源码解析
随着应用变大,通常会将根 reducer 拆分成多个独立的 reducers(每个负责状态的不同部分)。combineReducers
辅助函数将这些 reducers 合并成一个单一的 reducer 函数。
javascript
// 简化版的 combineReducers
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers);
const finalReducers = {};
// 过滤出有效的 reducer(函数类型)
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i];
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key];
}
}
const finalReducerKeys = Object.keys(finalReducers);
// 返回合并后的 reducer
return function combination(state = {}, action) {
let hasChanged = false; // 标记 state 是否改变
const nextState = {};
// 遍历每个 reducer
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i];
const reducer = finalReducers[key];
const previousStateForKey = state[key]; // 当前 key 的旧 state
const nextStateForKey = reducer(previousStateForKey, action); // 执行 reducer 计算新 state
if (typeof nextStateForKey === 'undefined') {
throw new Error(`Reducer "${key}" returned undefined when handling action of type "${action.type}".`);
}
nextState[key] = nextStateForKey;
// 检查此部分 state 是否改变(引用比较)
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;
// 如果 state 改变则返回新 state,否则返回旧 state
return hasChanged ? nextState : state;
};
}
2.3 applyMiddleware
源码解析
Middleware(中间件)是扩展 Redux 功能的主要方式,它提供了一个第三方扩展点在 dispatch action 和 action 到达 reducer 之间。常用于日志记录、异步操作等。
javascript
// 简化版的 applyMiddleware
export default function applyMiddleware(...middlewares) {
// 返回一个 enhancer,它接收 createStore 并返回增强后的 createStore
return (createStore) => (reducer, preloadedState, enhancer) => {
// 先创建 store
const store = createStore(reducer, preloadedState, enhancer);
let dispatch = () => {
throw new Error('Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.');
};
// 提供给中间件的 store API
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
};
// 将每个中间件传入 middlewareAPI 执行,得到一串链式函数
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 组合中间件:例如 [A, B, C] 变成 A(B(C(dispatch)))
dispatch = compose(...chain)(store.dispatch);
// 返回增强了 dispatch 的 store
return {
...store,
dispatch
};
};
}
// compose 工具函数:将多个函数组合起来,例如 compose(f, g, h) 将生成 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
if (funcs.length === 0) return arg => arg;
if (funcs.length === 1) return funcs[0];
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
中间件的模板代码通常遵循以下结构:
javascript
const middleware = store => next => action => {
// 在 dispatch 前可以做些什么
console.log('dispatching', action);
// 调用下一个中间件(或最终的 store.dispatch),传递 action
let result = next(action);
// 在 dispatch 后可以做些什么
console.log('next state', store.getState());
return result; // 通常返回 dispatch 的结果
};
3 React-Redux 原理与源码分析
React-Redux 是连接 React 和 Redux 的官方绑定库,它使得 React 组件能够方便地从 Redux store 中读取数据,并 dispatch actions。
3.1 Provider
组件
Provider
是一个 React 组件,它使用 React 的 Context API 将 Redux store 传递给所有子孙组件。
jsx
// Provider 简化源码
function Provider({ store, context, children }) {
// 利用 useMemo 根据 store 变化创建 contextValue
const contextValue = useMemo(() => {
// 创建一个根 Subscription(订阅)实例
const subscription = new Subscription(store);
subscription.onStateChange = subscription.notifyNestedSubs;
return { store, subscription };
}, [store]);
const previousState = useMemo(() => store.getState(), [store]);
useEffect(() => {
const { subscription } = contextValue;
subscription.trySubscribe(); // 发起订阅
if (previousState !== store.getState()) {
subscription.notifyNestedSubs(); // 如果 state 改变,立即通知订阅者
}
// 清理函数:组件卸载时取消订阅
return () => {
subscription.tryUnsubscribe();
subscription.onStateChange = null;
};
}, [contextValue, previousState]);
const Context = context || ReactReduxContext;
// 通过 Context.Provider 传递 contextValue
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
3.2 connect
高阶组件 (HOC) 与 Hooks
connect
是一个高阶组件,它负责连接 React 组件和 Redux store。
传统 connect
函数简化概念:
javascript
// connect 函数声明示例
connect(
mapStateToProps?,
mapDispatchToProps?,
mergeProps?,
options?
)(MyComponent);
现代 React-Redux Hooks: 在现代函数式组件中,更推荐使用 React-Redux 提供的 Hooks:
-
useSelector
: 从 store 的 state 中提取所需的数据。javascriptconst counter = useSelector(state => state.counter);
-
useDispatch
: 返回 store 的dispatch
方法,用于分发 actions。javascriptconst dispatch = useDispatch(); // ... dispatch({ type: 'INCREMENT' });
3.3 Subscription
类
Subscription
是 React-Redux 实现嵌套订阅 和高效更新的核心。它管理着一组监听器(listeners),并在 state 变化时通知它们。
javascript
// Subscription 类简化源码
export default class Subscription {
constructor(store, parentSub) {
this.store = store;
this.parentSub = parentSub; // 父级订阅器
this.unsubscribe = null;
this.listeners = nullListeners; // 监听器集合
this.handleChangeWrapper = this.handleChangeWrapper.bind(this);
}
// 添加嵌套订阅
addNestedSub(listener) {
this.trySubscribe();
return this.listeners.subscribe(listener); // 将监听器加入列表
}
// 通知所有监听器
notifyNestedSubs() {
this.listeners.notify();
}
// 处理变化的包装器
handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange(); // 通常触发组件的更新检查
}
}
// 尝试订阅:如果有父订阅器,则添加到父订阅器,否则直接订阅 store
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper) // 嵌套订阅
: this.store.subscribe(this.handleChangeWrapper); // 直接订阅 store
this.listeners = createListenerCollection(); // 创建监听器集合
}
}
// 取消订阅
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe();
this.unsubscribe = null;
this.listeners.clear();
this.listeners = nullListeners;
}
}
}
React-Redux 通过 Provider
创建一个根级的 Subscription
,并为每个 connect
后的组件或使用 Hooks 的组件创建嵌套的 Subscription
。这样就形成了一个订阅树,当 state 变化时,能够精确地通知到相关的组件,避免不必要的渲染,优化了性能。
4 Redux 中间件与异步操作
默认情况下,Redux 的 dispatch 是同步的。为了处理异步操作(如 API 调用),需要使用中间件。
4.1 redux-thunk
中间件
redux-thunk
是最常用的异步中间件之一,它允许 action creator 返回一个函数(而不仅仅是一个对象)。
源码及其原理:
javascript
// redux-thunk 核心源码
function createThunkMiddleware(extraArgument) {
// 中间件标准格式:store => next => action => {}
return ({ dispatch, getState }) => (next) => (action) => {
// 如果 action 是一个函数,则执行它,并传入 dispatch 和 getState
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 否则,将 action 传递给下一个中间件或最终的 reducer
return next(action);
};
}
const thunk = createThunkMiddleware();
export default thunk;
使用示例:
javascript
// 同步 Action Creator
const incrementSync = (amount) => ({ type: 'INCREMENT', payload: amount });
// 异步 Action Creator (thunk)
const incrementAsync = (amount) => {
return (dispatch, getState) => { // 返回一个函数(thunk)
setTimeout(() => {
dispatch({ type: 'INCREMENT', payload: amount }); // 异步完成后 dispatch
// 可以在这里访问当前的 state: const currentState = getState();
}, 1000);
};
};
// 在组件中分发
dispatch(incrementAsync(5));
4.2 其他异步中间件
除了 redux-thunk
,还有其他流行的异步中间件,如 redux-saga
(使用 Generator 函数管理复杂的异步流程)和 redux-observable
(基于 RxJS 响应式编程)。
5 总结与最佳实践
5.1 Redux 数据流
Redux 遵循严格的单向数据流:
- 状态存储在唯一的 Store 中。
- 视图基于 Store 中的 State 进行渲染。
- 当用户交互发生时,视图会 dispatch 一个 Action。
- Store 调用 Reducer,并传入当前 State 和 Action。Reducer 返回一个新的 State。
- Store 更新为新的 State,并通知所有订阅了 state 变化的视图。
- 视图根据新的 State 重新渲染。
5.2 不可变更新(Immutability)
Reducer 必须是纯函数 ,且禁止直接修改 state 。你必须使用不可变更新模式。
错误示例:
javascript
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
state.push(action.text); // ❌ 错误:直接修改了原 state!
return state;
default:
return state;
}
}
正确示例(使用展开运算符):
javascript
function todoReducer(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.text]; // ✅ 正确:返回新数组
case 'TOGGLE_TODO':
return state.map((todo, index) =>
index === action.index
? { ...todo, completed: !todo.completed } // ✅ 正确:返回新对象
: todo
);
default:
return state;
}
}
对于更复杂的不可变更新,可以使用 immer
等库来简化操作。
5.3 开发者工具
Redux DevTools Extension 是一个强大的调试工具,可以记录 action 日志、检查 state 快照、进行"时间旅行"调试等。可通过中间件启用:
javascript
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(thunk, logger)) // 使用 composeWithDevTools 包装
);
Redux 的架构和约束虽然在一定程度上增加了代码量,但其带来的可预测性、可调试性和可维护性使其在复杂的大型应用中非常有价值。