Redux源码分析(1) - Redux介绍及使用

366 阅读7分钟

1、Redux生态的介绍

  关于 Redux 的介绍可以参考: Redux 中文文档。Redux 是 JavaScript 状态容器,提供可预测化的状态管理。通常在 React 中使用 Redux 时,还使用到React-Redux、Redux-Saga。

  Redux作为全局的状态管理器,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。唯一改变 state 的方法就是触发 action,action 会通过执行 reducer 来改变 state 的值, state 的改变会引起视图层的改变,即 Component 的改变。Redux 的工作流如下图所示:

  在实际开发过程中,除 action 和 reducer 之外,还会用到一些中间件 middleware ,中间件本质就是在 dispatch 更改 reducer 之前做一些操作,这里涉及到 Redux 的一个重要 api:applyMiddleware ,在后续篇幅中会详细介绍。在实际的业务开发中,将所有更新 state 的逻辑都放入到单个 reducer 函数中都将会让程序变得不可维护,因此对 reducer 的拆分是很有必要的,这里也涉及到 Redux 的另外一个重要 api:combineReducers ,关于这部分在后文也会详细分析。如果加上这部分分析, Redux 的工作流就可以用下图更加详细的说明。

   Redux 作为全局的状态管理器,本身与 React 没有直接关系的。如果让 Redux 的状态能够在 React 的视图组件中展示,并且在视图组件可以更改 Redux 的状态,这就依赖于 React-Redux 。React-Redux 的核心是 Provider 组件 和 connect 方法 。

   Provider 的本质就是 React 的 context属性 ,通过在将 Provider 组件包裹 组件,并传入 Redux 的 store 属性,这样在 的后代组件中就可以通过获取到 store 中 state 的值。

<Provider store={store}>
    <App />
</Provider>,

   connect 方法的作用从字面上可以理解为:将 Redux 中 store 和 视图 View 关联起来。connect 本质是一个高阶函数。其中mapStateToProps可以解释为:将 store 中的 state 的映射到 Component 中,完成 Model 层到 View 层的展示。mapDispatchToProps 可以解释为:如何从 Component 中去更改 store 中的 state,完成从 View 层到 Model 层的改变。

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

   当然在 Redux 的 store 中的 state 发生变化,也会完成 Redux 的订阅 subscribe 的执行,也包括 mapStateToProps、 mapDispatchToProps 等方法的重新执行 ,关于 react-redux 可后续篇幅专门分析。

   Redux-Saga 本身就是 Redux 的一个中间件 middleware ,是为了解决 Redux 的一些异步的处理, 是通过 ES6 的 Generator 去实现的,让异步流程可以通过同步的方式去实现。

2、如何使用Redux

   了解了 Redux 的一些生态介绍之后,我们有必要看看 Redux 是如何使用,只有在熟练使用的基础上,我们才能更好的对源码有清晰的认识。下边截取了一些重要的代码,完整代码克制 克隆

2.1 项目入口引用

   在项目的入口文件处,分别引用了 react-Redux 中的 Provider 组件;redux 中的 createStore 方法和 applyMiddleware 方法,项目的 reducer 定义在 reducer.js 页面中。为了后续源码解读中能够更好的阐述中间件部分,此处定义了两个简单的中间件 Logger 和 Test。中间件本质是一个高阶函数,分别接受 store 、next、 action 参数,通过调用 applyMiddleware方法将中间件组合起来使用,这里先给出结论,后续中间件的分析中会详细说明:中间件的组合使用本质上就是改变 next 的值,其中 store 对应为 Redux 的数据模型 store, action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。

//index.js

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from './redux';
import rootReducer from './reducers';
import App from './components/App';

// middleware
const Logger = (store) => (next) => (action) => {
    console.info('logger start');
    let result = next(action);
    console.info('logger end');
};
const Test = (store) => (next) => (action) => {
    console.info('test start');
    let result = next(action);
    console.info('test end');
};

let store = createStore(rootReducer, applyMiddleware(Logger, Test));

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
);

   createStore 方法的官方介绍如下,简单来讲:reducer 既可以是单个 reducer 对象 也可以是通过 combineReducers 组合的 reducer 对象。preloadedState 如果存在则表示 reducer 的初始值,如果 reducer 参数是通过 combineReducers 组合生成的,则 preloadedState 必须是一个对象,key 值必须与 combineReducers时的key值对应 (不然 redux 怎么知道初始化那个 reducer )。enhancer 的官方解释比较难懂,可以简单理解为 enhancer 就是通过传入中间件到 applyMiddleware 方法产生的结果。

   createStore(reducer, [preloadedState], enhancer) 创建一个 Redux store 来以存放应用中所有的 state。 应用中应有且仅有一个 store。

参数

  • reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。

  • [preloadedState] (any): 初始时的 state。 在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。

  • enhancer (Function): Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口。

2.2 reducer的定义

   reducer 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。以 实例中 todos 为例,state 默认为返回空数组 [] , 根据 action.type 的不同类型返回不同的值。在当期实例中,reducer 是通过 combineReducers 组合生成的, 在实际开发中所有 state 和 改变 state 的逻辑放在一个 reducer 中显然是不合适,combineReducers 的意义就在对各个业务模块的 reducer 进行组合,最后传递给 redux 的 createStore 方法,继而生成一个的状态数。

   合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

   官方文档的解释可以简单理解为:combineReducers 会将各个 reducer 按照 key 值组合(当前实例是通过 ES6 语法简写),在你 dispatch(action) 时,也会根据你 key 值来改变 store 中 对应的 state 的值。

// rootReduce.js

import { combineReducers } from '../redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter
})


//todos.js

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(
        todo =>
          todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    default:
      return state
  }
}

2.3 组件内使用

   此示例中通过 react-redux 将 redux 和 react 关联起来,根据上文的介绍 react-redux 的 connect 方法通过 :

  • mapStateToProps 实现数据从 store 传递到 component,由于当前实例中 reducer 是通过 combineReducers 组合而成,因此在引用时就需要通过对应的 key 去引用 :state.todos, state.visibilityFilter ( 类似于命名空间的概念)。
  • mapDispatchToProps 可以让 component 能够改变 store 的状态。当 mapDispatchToProps 返回是函数时,默认的参数是 store.dispatch。
// Component

const TodoList = ({ todos, toggleTodo }) => (
  <ul>
    {todos.map(todo => (
      <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
    ))}
  </ul>
)

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

2.4 小结

  以上demo实例基本上涵盖了redux 的基本使用,并对于 react-redux 也有一些简单的介绍,完整的理解上述demo的流程,对于后续详细源码分析大有益处。


Redux源码分析(1) - Redux介绍及使用

redux源码分析(2) - createStore

Redux源码分析(3) - applyMiddleware

Redux源码分析(4) - combineReducers和bindActionCreators