案例项目

  1. # 为了方便讲解,我写了一个 demo,请先安装一下
  2. git clone https://github.com/GYunZhi/cnode.git
  3. cd cnode && yarn
  4. yarn start

三大原则

单一数据源

整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。

  1. console.log(store.getState())
  2. /* 输出
  3. {
  4. visibilityFilter: 'SHOW_ALL',
  5. todos: [
  6. {
  7. text: 'Consider using Redux',
  8. completed: true,
  9. },
  10. {
  11. text: 'Keep all state in a single tree',
  12. completed: false
  13. }
  14. ]
  15. }
  16. */

State 是只读的

唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

  1. store.dispatch({
  2. type: 'COMPLETE_TODO',
  3. index: 1
  4. })
  5. store.dispatch({
  6. type: 'SET_VISIBILITY_FILTER',
  7. filter: 'SHOW_COMPLETED'
  8. })

使用纯函数来执行修改

为了描述 action 如何改变 state tree ,你需要编写 reducers。

Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。``

  1. function visibilityFilter(state = 'SHOW_ALL', action) {
  2. switch (action.type) {
  3. case 'SET_VISIBILITY_FILTER':
  4. return action.filter
  5. default:
  6. return state
  7. }
  8. }
  9. function todos(state = [], action) {
  10. switch (action.type) {
  11. case 'ADD_TODO':
  12. return [
  13. ...state,
  14. {
  15. text: action.text,
  16. completed: false
  17. }
  18. ]
  19. case 'COMPLETE_TODO':
  20. return state.map((todo, index) => {
  21. if (index === action.index) {
  22. return Object.assign({}, todo, {
  23. completed: true
  24. })
  25. }
  26. return todo
  27. })
  28. default:
  29. return state
  30. }
  31. }
  32. import { combineReducers, createStore } from 'redux'
  33. let reducer = combineReducers({ visibilityFilter, todos })
  34. let store = createStore(reducer)

就是这样,现在你应该明白 Redux 是怎么回事了。

基本概念

redux中有三个基本概念,Action,Reducer,Store。

Action

官方的介绍:

Actions are payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store using store.dispatch().

中文:

Actions 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。用法是通过 store.dispatch() 把 action 传到 store。

总结:

Action 有两个作用。

  1. 用Action来分辨具体的执行动作。比如是create ?还是delete?或者是update?
  2. 操作数据首先得有数据。比如添加数据得有数据,删除数据得有ID,action携带了这些数据。

Reducer

官方的介绍:

Actions describe the fact that something happened, but don’t specify how the application’s state changes in response. This is the job of a reducer.

中文:

Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。这是 reducer 要做的事情。

总结:

Action就像leader,告诉我们应该做哪些事,并且提供数据,真正干活的是苦逼的Reducer。

Store

一个应用只有一个Store。一个应用只有一个Store。一个应用只有一个Store。

重要的事情放在前面说,而且说三遍。

官方的介绍:

In the previous sections, we defined the actions that represent the facts about “what happened” and the reducers that update the state according to those actions.

The Store is the object that brings them together. The store has the following responsibilities:

  • Holds application state;
  • Allows access to state via getState();
  • Allows state to be updated via dispatch(action);
  • Registers listeners via subscribe(listener).

翻译成中文:

上面章节中,我们学会了使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。

Store 就是把它们联系到一起的对象。Store 有以下职责:

  • 保存应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器。

总结:

Store提供了一些方法。让我们很方便的操作数据。

我们不用关心Reducer和Action是怎么关联在一起的,Store已经帮我们做了这些事

流程图

Redux 指北 - 图1

详细介绍

这部分主要讲解redux如何在项目中使用。

Action

Action 是一个普通对象。

redux约定 Action 内使用一个字符串类型的 type 字段来表示将要执行的动作。

  1. {
  2. type: 'ADD_ITEM'
  3. }

除了 type 之外,Action可以存放一些其他的想要操作的数据。例如:

  1. {
  2. type: 'ADD_ITEM',
  3. text: '我是Berwin'
  4. }

上面例子表示

  1. 我要创建一条数据
  2. 创建的数据为大概是这样的
  1. {
  2. text: '我是Berwin'
  3. }

但在实际应用中,我们需要一个函数来为我们创建Action。这个函数叫做actionCreator。它看起来是这样的:

  1. function addItem(text) {
  2. return {
  3. type: types.ADD_ITEM,
  4. text
  5. }
  6. }

Reducer

Reducer 是一个普通的回调函数,其函数签名为reducer(previousState, action)。

当它被Redux调用的时候会为他传递两个参数StateAction

Reducer会根据 Action 的type来对旧的 State 进行操作,返回新的State。

看起来是下面这样的:

  1. const action = {
  2. type: 'ADD_TODO',
  3. text: 'Learn Redux'
  4. };

Reducer很简单,但有三点需要注意

  1. 不要修改 state
  2. 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。
  3. 如果没有旧的State,就返回一个initialState,这很重要!!!

这是一部分核心源码:

  1. // currentState 是当前的State,currentReducer 是当前的Reducer
  2. currentState = currentReducer(currentState, action);

如果在default或没有传入旧State的情况下不返回旧的State或initialState,那么当前的State会被重置为undefined!!

在使用combineReducers方法时,它也会检测你的函数写的是否标准。如果不标准,那么会抛出一个大大的错误!!

combineReducers

真正开发项目的时候State会涉及很多功能,在一个Reducer处理所有逻辑会非常混乱,,所以需要拆分成多个小Reducer,每个Reducer只处理它管理的那部分State数据。然后在由一个主rootReducers来专门管理这些小Reducer

Redux提供了一个方法 combineReducers 专门来管理这些小Reducer。

它看起来是下面这样:

  1. /**
  2. * 这是一个子Reducer
  3. * @param State
  4. * @param Action
  5. * @return new State
  6. */
  7. let list = (state = [], action) => {
  8. switch (action.type) {
  9. case ADD_ITEM:
  10. return [createItem(action.text), ...state]
  11. default:
  12. return state
  13. }
  14. }
  15. // 这是一个简单版的子Reducer,它什么都没有做。
  16. let category = (state = {}, action) => state;
  17. /**
  18. * 这是一个主Reducer
  19. * @param State
  20. * @param Action
  21. * @return new State
  22. */
  23. let rootReducers = combineReducers({list, category});

combineReducers 生成了一个类似于Reducer的函数。为什么是类似于,因为它不是真正的Reducer,它只是一个调用Reducer的函数,只不过它接收的参数与真正的Reducer一模一样~

这是一部分核心源码:

  1. function combineReducers(reducers) {
  2. // 过滤reducers,把非function类型的过滤掉
  3. var finalReducers = pick(reducers, (val) => typeof val === 'function');
  4. // 一开始我一直以为这个没啥用,后来我发现,这个函数太重要了。它在一开始,就已经把你的State改变了。变成了,Reducer的key 和 Reducer返回的initState组合。
  5. var defaultState = mapValues(finalReducers, () => undefined);
  6. return function combination(state = defaultState, action) {
  7. // finalReducers 是 reducers
  8. var finalState = mapValues(finalReducers, (reducer, key) => {
  9. // state[key] 是当前Reducer所对应的State,可以理解为当前的State
  10. var previousStateForKey = state[key];
  11. var nextStateForKey = reducer(previousStateForKey, action);
  12. return nextStateForKey;
  13. });
  14. // finalState 是 Reducer的key和stat的组合。。
  15. }
  16. }

从上面的源码可以看出,combineReducers 生成一个类似于Reducer的函数combination

当使用combination的时候,combination会把所有子Reducer都执行一遍,子Reducer通过action.type 匹配操作,因为是执行所有子Reducer,所以如果两个子Reducer匹配的action.type是一样的,那么都会成功匹配。

Store

上面已经介绍什么是Store,以及它是干什么的,这里我就讲讲如何创建Store,以及如何使用Store的方法。

创建Store非常简单。createStore 有两个参数,Reducer 和 initialState。

  1. let store = createStore(rootReducers, initialState);

store有四个方法。

  1. getState: 获取应用当前State。
  2. subscribe:添加一个变化监听器。
  3. dispatch:分发 action。修改State。
  4. replaceReducer:替换 store 当前用来处理 state 的 reducer。

常用的是dispatch,这是修改State的唯一途径,使用起来也非常简单,他看起来是这样的~

  1. /**
  2. * 创建Action
  3. * @param 添加的数据
  4. * @return {Object} Action
  5. */
  6. function addItem(text) {
  7. return {
  8. type: types.ADD_ITEM,
  9. text
  10. }
  11. }
  12. // 新增数据
  13. store.dispatch(addItem('Read the docs'));

这是一部分核心源码:

  1. function dispatch(action) {
  2. // currentReducer 是当前的Reducer
  3. currentState = currentReducer(currentState, action);
  4. listeners.slice().forEach(function (listener) {
  5. return listener();
  6. });
  7. return action;
  8. }

可以看到其实就是把当前的Reducer执行了。并且传入State和Action。

State哪来的?

State其实一直在Redux内部保存着。并且每次执行currentReducer都会更新。在上面代码第一行可以看到。

React-Redux

Redux 是独立的,它与 React 没有任何关系。React-Redux 是官方提供的一个库,用来结合 redux和 react 的模块。

React-Redux提供了两个接口Providerconnect

Provider

Provider是一个React组件,它的作用是保存 store 给子组件中的 connect 使用。

  1. 通过 getChildContext 方法把 store 保存到 context 里。
  2. 后面 connect 中会通过 context 读取 store。

它看起来是这个样子的:

  1. <Provider store={this.props.store}>
  2. <h1>Hello World!</h1>
  3. </Provider>

这是一部分核心源码:

  1. getChildContext() {
  2. return { store: this.store }
  3. }
  4. constructor(props, context) {
  5. super(props, context)
  6. this.store = props.store
  7. }

可以看到,先获取store,然后用 getChildContext 把store保存起来~

connect

connect 会把 State 和 dispatch 转换成 props 传递给子组件。它看起来是下面这样的:

  1. import * as actionCreators from './actionCreators'
  2. import { bindActionCreators } from 'redux'
  3. function mapStateToProps(state) {
  4. return { todos: state.todos }
  5. }
  6. function mapDispatchToProps(dispatch) {
  7. return { actions: bindActionCreators(actionCreators, dispatch) }
  8. }
  9. export default connect(mapStateToProps, mapDispatchToProps)(Component)

它会让我们传递一些参数:mapStateToProps,mapDispatchToProps,mergeProps(可不填)和React组件。

之后这个方法会进行一系列的黑魔法,把state,dispatch转换成props传到React组件上,返回给我们使用。

mapStateToProps:

mapStateToProps 是一个普通的函数。

当它被connect调用的时候会为它传递一个参数State。

mapStateToProps需要负责的事情就是 返回需要传递给子组件的State,返回需要传递给子组件的State,返回需要传递给子组件的State,(重要的事情说三遍。。。。)然后connect会拿到返回的数据写入到react组件中,然后组件中就可以通过props读取数据啦~~~~

它看起来是这样的:

  1. function mapStateToProps(state) {
  2. return { list: state.list }
  3. }

因为stat是全局State,里面包含整个项目的所有State,但是我不需要拿到所有State,我只拿到我需要的那部分State即可,所以需要返回 state.list 传递给组件

mapDispatchToProps:

与mapStateToProps很像,mapDispatchToProps也是一个普通的函数。

当它被connect调用的时候会为它传递一个参数dispatch。

mapDispatchToProps负责返回一个 dispatchProps

dispatchProps 是actionCreator的key和dispatch(action)的组合。

dispatchProps 看起来长这样:

  1. {
  2. addItem: (text) => dispatch(action)
  3. }

connect 收到这样的数据后,会把它放到React组件上。然后子组件就可以通过props拿到addItem并且使用啦。

  1. this.props.addItem('Hello World~');

如果觉得复杂,不好理解,,那我用大白话描述一下

就是通过mapDispatchToProps这个方法,把actionCreator变成方法赋值到props,每当调用这个方法,就会更新State。。。。额,,这么说应该好理解了。。

bindActionCreators:

但如果我有很多个Action,总不能手动一个一个加。Redux提供了一个方法叫 bindActionCreators

bindActionCreators 的作用就是将 Actionsdispatch 组合起来生成 mapDispatchToProps 需要生成的内容。

它看起来像这样:

  1. let actions = {
  2. addItem: (text) => {
  3. type: types.ADD_ITEM,
  4. text
  5. }
  6. }
  7. bindActionCreators(actions, dispatch); // @return {addItem: (text) => dispatch({ type: types.ADD_ITEM, text })}

这是一部分核心源码:

  1. function bindActionCreator(actionCreator, dispatch) {
  2. return (...args) => dispatch(actionCreator(...args));
  3. }
  4. // mapValues: map第一个参数的每一项,返回对象,key是key,value是第二个参数返回的数据
  5. /*
  6. * mapValues: map第一个参数的每一项,返回对象,key是key,value是第二个参数返回的数据
  7. * @param actionCreators
  8. * @param dispatch
  9. * @return {actionKey: (...args) => dispatch(actionCreator(...args))}
  10. */
  11. export default function bindActionCreators(actionCreators, dispatch) {
  12. return mapValues(actionCreators, actionCreator =>
  13. bindActionCreator(actionCreator, dispatch)
  14. );
  15. }

可以看到,bindActionCreators 执行这个方法之后,它把 actionCreators 的每一项的 key 不变,value 变成 dispatch(actionCreator(...args)) 这玩意,这表示,actionCreator 已经变成了一个可执行的方法,执行这个方法,就会执行 dispatch 更新数据。。

加了React-Redux之后的流程图:

Redux 指北 - 图2