Redux 是一个可预测的状态容器,它为 JavaScript 应用提供了一个中心化的存储状态的地方。在 Redux 中,有五个核心函数构成了其架构的基础。本文对 createStore
、combineReducers
、applyMiddleware
、bindActionCreators
和 compose
这五个函数进行详细介绍,相信掌握这五个函数能够为之后使用 Redux 打下坚实的基础。
由于这五个函数的开头字母,我们可以将这五个单词简单记为:abccc
。
Redux 状态转移图
graph TB
A[Action Creator] -->|dispatches| B[Action]
B -->|received by| C[Redux Store]
C -->|updates| D[Redux State]
C -->|notifies| E[Reducers]
E -->|generate new| F[New State]
F -->|updates| D
D -->|selects| G[React Components]
G -->|triggers when needed| A
Redux 状态转移图及核心函数
graph TB
A[Action Creator] -->|dispatches| B[Action]
B -->|is received by| C[Redux Middleware]
C -->|passes action to| D[Redux Store]
D -->|uses| E[combineReducers]
E -->|to combine| F[Multiple Reducers]
F -->|generate| G[Initial State]
D -->|updates based on| H[Redux State]
D -->|notifies| I[Reducers]
I -->|generate new| J[New State]
J -->|updates| H
H -->|selects using| K[selectors]
K -->|provides data to| L[React Components]
M[bindActionCreators] -->|binds| A
N[applyMiddleware] -->|wraps| C
O[createStore] -->|creates| D
P[compose] -->|combines| Q[Enhancers \nincluding middleware]
Q -->|enhances| O
L -->|triggers when needed| A
1. createStore
含义与功能:
createStore
是 Redux 的一个函数,用于创建 Redux store。这个 store 对象是 Redux 应用中状态的中心仓库,它包含了应用程序的状态树,并提供了更改状态的方法。createStore
接受一个 reducer 函数作为其主要参数,这个 reducer 函数负责根据 actions 更新状态。
函数签名:
createStore(reducer: Function, preloadedState?: any, enhancer?: Function) => Store
reducer
: 一个函数,它接收当前状态和 action,并返回新的状态。preloadedState
: (可选) 预先加载的状态,用于初始化 store。enhancer
: (可选) 一个或多个 store enhancer,用于增强 store 的功能。
代码示例:
import { createStore } from 'redux';
// rootReducer 是一个函数,它接收整个应用的状态和 action
const rootReducer = (state = {}, action) => {
switch (action.type) {
// 处理不同的 action 类型,并更新状态
default:
return state;
}
};
const store = createStore(rootReducer);
Redux 使用过程中的角色:
在 Redux 架构中,store
是中心节点,它连接了以下几个关键组件:
- Actions: 描述状态更改的普通对象。
- Reducers: 根据 actions 更新状态的纯函数。
- UI Components: 应用的界面组件,它们订阅 store 的状态变化,并在变化时重新渲染。
当一个 action 被 dispatch 到 store 时,store 使用 reducer 来计算新的状态,然后通知所有订阅的监听器(UI 组件),这些监听器随后可以根据新状态进行更新。
联系 actions, reducers, UI 组件: Redux 通过单向数据流来联系这些组件:
- UI 组件通过调用 store 的
dispatch
方法分派一个 action。 - Store接收到 action 后,使用 reducer 函数计算新的状态。
- Store将新状态通知给所有订阅的 UI 组件。
- UI 组件使用新的 state 来更新渲染。
为什么叫做 rootReducer:
rootReducer
是一个 reducer 函数,它是所有子 reducer 的组合。在大型应用中,状态可能被分割成多个部分,每个部分由不同的 reducer 管理。rootReducer
负责将这些子 reducer 组合起来,形成一个单一的 reducer 函数,这样 createStore
就可以使用它作为整个应用状态的管理者。
rootReducer 的类型和含义:
- 参数:
state
(当前状态,任何类型) 和action
(一个描述状态更改的对象)。 - 返回值: 新的状态对象,类型与输入的
state
相同。
通过这种方式,rootReducer
确保了状态的不可变性,因为每次状态更新都返回了一个新的状态对象。
2. combineReducers
含义与功能:
combineReducers
是 Redux 提供的一个辅助函数,用于将多个 reducer 函数合并成一个单一的 reducer 函数。这个单一的 reducer 函数会根据传入的 action 的 type
属性,将状态更新委托给相应的子 reducer。这种合并对于构建大型应用非常有帮助,因为它允许开发者将状态分割成多个部分,每个部分由不同的 reducer 独立管理。
参数与返回值:
combineReducers(reducers: Object) => Reducer
reducers
参数是一个对象,其属性值是各个子 reducer 函数。- 返回值是一个单一的 reducer 函数,它接收整个应用的状态和 action,并返回更新后的状态。
代码示例对比:
结合前的 reducer 使用方式
在结合之前,reducerA
和 reducerB
的内容通常是分别处理的,而不是写在一起的。如果需要在一个 reducer 中处理两种状态的更新,代码可能会变得冗长和难以管理。
// 假设的单个 reducer 处理两种状态更新的示例(不推荐)
const uncombinedReducer = (state = { count: 0, value: '' }, action) => {
switch (action.type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1,
};
case 'SET_VALUE':
return {
...state,
value: action.payload,
};
default:
return state;
}
};
const store = createStore(uncombinedReducer);
// 调用代码示例
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'SET_VALUE', payload: 'Hello, Redux!' });
在这个例子中,我们有一个 combinedReducer
,它同时处理了 INCREMENT
和 SET_VALUE
两种 action。然而,随着应用状态管理的复杂性增加,这种方式会变得难以维护。
结合后的 reducer 使用方式
使用 combineReducers
后,代码更加模块化和清晰。
// reducerA 的初始状态和相关 reducer 函数
const initialStateA = {
count: 0
};
function reducerA(state = initialStateA, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
// reducerB 的初始状态和相关 reducer 函数
const initialStateB = {
value: ''
};
function reducerB(state = initialStateB, action) {
switch (action.type) {
case 'SET_VALUE':
return { ...state, value: action.payload };
default:
return state;
}
}
const rootReducer = combineReducers({
reducerA,
reducerB
});
const store = createStore(rootReducer);
// 调用代码示例
store.dispatch({ type: 'INCREMENT' }); // 仅更新 reducerA 的状态
store.dispatch({ type: 'SET_VALUE', payload: 'Hello, Redux!' }); // 仅更新 reducerB 的状态
在这个例子中,我们有两个独立的 reducer,reducerA
和 reducerB
,它们通过 combineReducers
被合并成一个单一的 reducer。这样,每个 reducer 只关注自己状态切片的更新逻辑,使得代码更加清晰和易于维护。
Redux 使用过程中的角色:
combineReducers
在 Redux 中起到了关键的角色,它允许开发者将大型状态树分解为多个小的状态切片,每个切片由独立的 reducer 管理。这种分离使得每个 reducer 可以专注于自己的状态更新逻辑,提高了代码的可读性和可维护性。
适用场景与意义:
当应用状态管理变得复杂时,使用 combineReducers
可以显著提高代码质量。通过将状态分割成多个部分,每个部分由不同的团队或开发者负责,可以提高开发效率、代码可读性和可维护性。这对于构建大型和复杂的前端应用至关重要。
3. applyMiddleware
含义与功能:
applyMiddleware
是一个函数,它允许你在 dispatch 动作的过程中添加中间件逻辑。中间件可以访问动作,执行副作用,如日志记录、异步操作或者在动作分派前后执行其他逻辑。
代码示例:
import { createStore, applyMiddleware } from 'redux';
// 导入 loggerMiddleware 是为了日志记录,这里我们也将导入一个简单的自定义中间件
import loggerMiddleware from 'redux-logger';
import simpleMiddleware from './simpleMiddleware';
const store = createStore(
rootReducer,
/* 初始状态可以在这里定义,或者省略以在 reducer 中定义默认值。
如果在此处定义,它将成为 createStore 的第二个参数。但在这个例子中,
我们关注的是中间件的应用,所以省略了初始状态参数。
如果需要设置初始状态,可以这样做:
createStore(rootReducer, initialState, applyMiddleware(loggerMiddleware, simpleMiddleware))
*/
applyMiddleware(loggerMiddleware, simpleMiddleware) // 可以同时应用多个中间件
);
// 下面是一个简单的中间件示例
// simpleMiddleware.js
const simpleMiddleware = store => next => action => {
console.log('Middleware start:', action);
let result = next(action); // 传递 action 到下一个中间件或 reducer
console.log('Middleware end:', store.getState());
return result;
};
export default simpleMiddleware;
Redux 使用过程中的角色:
applyMiddleware
为 store 提供了中间件机制,这些中间件可以改变 Redux 的 dispatch 过程。通过串联一系列的中间件函数,我们可以在动作被分发到 reducer 之前或之后执行自定义代码。
如何同时应用多个中间件:
在 applyMiddleware
函数中,你可以传递多个中间件作为参数,它们会按照传递的顺序依次执行。每个中间件都会接收 store
、next
和 action
三个参数,并且需要返回一个函数。这个函数在调用时会接收 action
并返回一个可能不同的 action
,或者像上面的例子那样简单地调用 next(action)
并处理前后的逻辑。
当我们想要在 Redux store 中同时应用多个中间件时,我们只需要将这些中间件作为参数依次传递给 applyMiddleware
函数即可。这些中间件会按照传递的顺序被串联起来,并且每个中间件都将有机会处理(或者改变)流向下一个中间件的 action
。
以下是如何同时应用多个中间件的示例:
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './reducers'; // 假设这是你的根 reducer
import middleware1 from './middleware1'; // 自定义的中间件1
import middleware2 from './middleware2'; // 自定义的中间件2
// 创建 store 时应用多个中间件
const store = createStore(
rootReducer,
applyMiddleware(middleware1, middleware2)
);
在这个例子中,middleware1
和 middleware2
是我们想要应用的两个中间件。当 store 中的 dispatch
方法被调用时,action
会首先经过 middleware1
,然后是 middleware2
,最后才会到达 reducer。
适用场景:
当你需要在 actions 分派的过程中添加日志记录、异步处理或者其他自定义逻辑时,使用 applyMiddleware
。例如,你可能想要在每个动作分发前后打印日志,或者处理异步动作(如 API 调用)并在完成后分发一个新的动作。
中间件原理:
中间件本质上是一个高阶函数,它接收 Redux 的 store
作为第一个参数,并返回一个函数。这个返回的函数接收另一个函数 next
作为参数,并再次返回一个函数。最后返回的这个函数接收一个 action
对象,并可以在调用 next(action)
之前和之后执行代码。通过这种方式,中间件能够“拦截”并可能修改传递给 reducer 的动作,或者在动作处理前后执行额外的逻辑。
4. bindActionCreators
含义与功能:
bindActionCreators
是一个实用函数,其名称中的“bind”意为“绑定”,“ActionCreators”指的是创建action的函数。这个函数的作用是将一组 action creators(动作创建器)绑定到一个特定的 dispatch
函数,并返回一个新的对象。在这个新对象中,原本需要手动调用 dispatch
的 action creators 现在可以直接调用,它们内部会自动进行 dispatch
。
函数参数与返回值:
- 参数:
actionCreators
: 一个包含多个 action creator 函数的对象。dispatch
: Redux store 的dispatch
函数,用于分发 actions。
- 返回值:
返回一个新的对象,其中包含了与原始
actionCreators
对象中相同的函数,但这些函数现在被绑定了dispatch
,因此可以直接调用以触发 action。
Action Creator 是啥: Action creator 是一个返回 action 对象的函数。在 Redux 中,actions 是将数据从应用传到 store 的有效载荷。它们是 store 状态的唯一来源。Action creator 就是用来创建这些 actions 的函数。
举例说明 Action Creator:
// 这是一个简单的 action creator 示例
function addTodo(text) {
return { type: 'ADD_TODO', text };
}
在上面的例子中,addTodo
是一个 action creator,它返回一个 action 对象,该对象包含一个类型 ADD_TODO
和相关的数据 text
。
为什么需要使用 bindActionCreators
函数:
使用 bindActionCreators
可以简化在 React 组件中分发 actions 的过程。通常,在组件中分发一个 action 需要显式调用 dispatch
函数。但当你使用 bindActionCreators
后,可以直接调用绑定的 action creators,无需每次都写出 dispatch
。
代码对比:
使用前:
import { useDispatch } from 'react-redux';
import { addTodo } from './actionCreators';
function TodoAddItem() {
const dispatch = useDispatch();
const handleAddTodo = () => {
dispatch(addTodo('Learn Redux'));
};
// ... 组件的其余部分
}
使用后:
import { useDispatch } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionCreators from './actionCreators';
function TodoAddItem() {
const dispatch = useDispatch();
const { addTodo } = bindActionCreators(actionCreators, dispatch);
const handleAddTodo = () => {
addTodo('Learn Redux'); // 直接调用,无需显式 dispatch
};
// ... 组件的其余部分
}
为什么不显式调用 dispatch
:
- 代码简化: 使用
bindActionCreators
可以避免在每次需要分发 action 时都写出dispatch
,使代码更加简洁。 - 可读性: 绑定的 action creators 可以像普通函数一样调用,提高了代码的可读性和可维护性。
- 组件复用: 当你有多个组件需要使用相同的 action creators 时,通过
bindActionCreators
可以方便地在多个组件间复用这些函数,而无需在每个组件中都写一遍dispatch
逻辑。
5. compose
compose
函数作为一个高阶函数,其核心理念是将一系列的函数从右至左依次执行,并将前一个函数的输出作为后一个函数的输入。下面是一个简单的 compose
实现,并展示了如何不与 Redux 关联而单独使用它。
代码示例:
// 一个简单的 compose 函数实现
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)));
}
// 定义几个简单的函数用于测试
function double(x) {
return x * 2;
}
function addOne(x) {
return x + 1;
}
function multiplyByThree(x) {
return x * 3;
}
// 使用 compose 组合这些函数
const composedFunction = compose(multiplyByThree, addOne, double);
// 测试 composedFunction
const result = composedFunction(5); // ((5 * 2) + 1) * 3 = 33
console.log(result); // 输出应为 33
在这个例子中,compose
函数将 multiplyByThree
、addOne
和 double
三个函数组合成一个新的函数 composedFunction
。当我们调用 composedFunction(5)
时,实际上相当于执行了 multiplyByThree(addOne(double(5)))
,最终输出结果为 33。
现在,将上面的内容和原始内容合并,得到完整的说明:
compose 函数的基础使用
compose
函数作为一个高阶函数,可以从右至左依次执行一系列的函数,并将前一个函数的输出作为后一个函数的输入。下面是一个简单的 compose
实现,并展示了如何不与 Redux 关联而单独使用它。
代码示例:
// 一个简单的 compose 函数实现
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)));
}
// 定义几个简单的函数用于测试
function double(x) {
return x * 2;
}
function addOne(x) {
return x + 1;
}
function multiplyByThree(x) {
return x * 3;
}
// 使用 compose 组合这些函数
const composedFunction = compose(multiplyByThree, addOne, double);
// 测试 composedFunction
const result = composedFunction(5); // ((5 * 2) + 1) * 3 = 33
console.log(result); // 输出应为 33
compose 在 Redux 中的应用
含义与功能:
在 Redux 中,compose
是一个高阶函数,它接受多个函数作为参数,这些函数通常是 store enhancers,然后返回一个新的函数。这个新函数是这些 enhancers 的组合,用于增强 Redux store 的功能。
代码示例:
import { createStore, applyMiddleware, compose } from 'redux';
import devToolsExtension from 'redux-devtools-extension';
import thunk from 'redux-thunk'; // 示例中间件
// 假设 rootReducer 是你已经定义的 reducer
const store = createStore(
rootReducer,
compose(
applyMiddleware(thunk /* 可以添加更多中间件 */),
devToolsExtension() // 集成 Redux DevTools
// 可以继续添加更多的 enhancers
)
);
Redux 使用过程中的角色:
在 Redux 中,compose
允许开发者灵活地组合多个 store enhancers,以此来增加诸如日志记录、崩溃报告、时间旅行调试等额外功能。
适用场景:
当你希望为你的 Redux store 添加多种功能时,比如同时应用中间件和 Redux DevTools,compose
就显得非常有用。它使得这些功能的集成变得更加简洁和模块化。
上述的 5 个函数构成了 Redux 架构的基础,并且它们在 Redux 应用的创建和运行过程中扮演着至关重要的角色。理解每个函数的作用和适用场景,可以帮助开发者更好地使用 Redux 来构建和管理复杂的应用状态。