Redux 前菜 -- 理解 Redux 中的五个核心函数

1,176 阅读13分钟

Redux 是一个可预测的状态容器,它为 JavaScript 应用提供了一个中心化的存储状态的地方。在 Redux 中,有五个核心函数构成了其架构的基础。本文对 createStorecombineReducersapplyMiddlewarebindActionCreatorscompose 这五个函数进行详细介绍,相信掌握这五个函数能够为之后使用 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 通过单向数据流来联系这些组件:

  1. UI 组件通过调用 store 的 dispatch 方法分派一个 action。
  2. Store接收到 action 后,使用 reducer 函数计算新的状态。
  3. Store将新状态通知给所有订阅的 UI 组件
  4. 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 使用方式

在结合之前,reducerAreducerB 的内容通常是分别处理的,而不是写在一起的。如果需要在一个 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,它同时处理了 INCREMENTSET_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,reducerAreducerB,它们通过 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 函数中,你可以传递多个中间件作为参数,它们会按照传递的顺序依次执行。每个中间件都会接收 storenextaction 三个参数,并且需要返回一个函数。这个函数在调用时会接收 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)
);

在这个例子中,middleware1middleware2 是我们想要应用的两个中间件。当 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

函数参数与返回值:

  • 参数:
    1. actionCreators: 一个包含多个 action creator 函数的对象。
    2. 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 函数将 multiplyByThreeaddOnedouble 三个函数组合成一个新的函数 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 来构建和管理复杂的应用状态。