在学习redux之前先了解一下纯函数:

函数式编程中有一个概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念

纯函数的维基百科定义:

React中redux的使用(11)

上面的定义会过于的晦涩,简单总结:

  • 确定的输入,一定会产生确定的输出;
  • 函数在执行过程中,不能产生副作用;
<script>
  // 下面是纯函数吗? 不是
  function printfNum(num) {
    return num + 20;
  }
  let num1 = 20;
  printfNum(num1);
  num = 30;
  // 如果把 let num1 改为 const num1 就是一个纯函数了

  // ....
</script>

React中的纯函数

纯函数还有很多的变种,但是我们只需要理解它的核心就可以了。

为什么纯函数在函数式编程中非常重要呢?

  • 因为你可以安心的写和安心的用;
  • 你在写的时候保证了函数的纯度,只是但是实现自己的业务逻辑即可,不需要关心传入的内容或者依赖其他的外部变量;
  • 你在用的时候,你确定你的输入内容不会被任意篡改,并且自己确定的输入,一定会有确定的输出;

React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改:

React中redux的使用(11)

在学习redux中,reducer也被要求是一个纯函数。

10.1redux介绍

Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。

JavaScript开发的应用程序已经变得越来越复杂了,它需要管理的状态越来越多,越来越复杂。

  • 这些状态包括服务器返回的数据、缓存数据、用户操作产生的数据等等,也包括一些UI的状态,比如某些元素是否被选中,是否显示加载动效,当前分页。
  • 管理不断变化的state是非常困难的
  • 状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
  • 当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;
  • React是在视图层帮助我们解决了DOM的渲染过程,但是State依然是留给我们自己来管理:

    • 无论是组件定义自己的state,还是组件之间的通信通过props进行传递;也包括通过Context进行数据之间的共享;
    • React主要负责帮助我们管理视图,state如何维护最终还是我们自己来决定:

Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;

Redux 除了和 React 一起用外,还支持其它界面库。它体小精悍(只有2kB,包括依赖),却有很强大的插件扩展生态。

React中redux的使用(11)

10.2redux核心概念

当我们有一个朋友列表需要管理:

  • 如果我们没有定义统一的规范来操作这段数据,那么整个数据的变化就是无法跟踪的;
  • 比如页面的某处通过products.push的方式增加了一条数据;
  • 比如另一个页面通过products[0].age = 25修改了一条数据;

整个应用程序错综复杂,当出现bug时,很难跟踪到底哪里发生的变化;

用普通对象描述:

React中redux的使用(11)

上面这种是不可追踪不可预测的!

1.核心概念:Action

Redux要求我们通过action来更新数据:

所有数据的变化,必须通过派发(dispatch)action来更新;

action是一个普通的JavaScript对象,用来描述这次更新的type和content;

比如下面就是几个更新的action:

React中redux的使用(11)

强制使用action的好处是可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是可跟追、可预测的;

目前我们的action是固定的对象(如果是这样的话要修改多少状态就会有多少action),真实应用中,我们会通过函数来定义,返回一个action;

2.核心概念:reducer

action只是描述了数据要发生改变,并没有描述应用如何更新 state。

而将state和action联系在一起,Reducers 指定了应用状态的变化如何响应 action 并发送到 store 的。

reducer是一个纯函数,接受旧的state和action,返回一个新的state。

需要谨记 reducer 一定要保持纯净。

// 第一个参数是es6的默认参数写法
function reducer(state = initialState, action) {
  switch (action.type) {
    case "INCRMENT":
      return { ...state, counter: state.counter + 1 };
    case "DECRMENT":
      return { ...state, counter: state.counter - 1 };
    case "Add_NUM":
      return { ...state, counter: state.counter + action.num }
    case "SUB_NUM":
      return { ...state, counter: state.counter - action.num }
    default:
      return state
  }
}
3.核心概念: Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

const redux = require("redux");
const store = redux.createStore(fn);

上面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。

前面两个核心概念是使用action描述 "发生了什么",reducer来根据它的参数来更新state的用法。

Store 就是把它们联系到一起的对象。

Store 有以下职责:

const redux = require("redux");

const initialState = {
  counter: 0
}

function reducer(state = initialState, action) {
  switch (action.type) {
    case "INCRMENT":
      return { ...state, counter: state.counter + 1 };
    case "DECRMENT":
      return { ...state, counter: state.counter - 1 };
    case "Add_NUM":
      return { ...state, counter: state.counter + action.num }
    case "SUB_NUM":
      return { ...state, counter: state.counter - action.num }
    default:
      return state
  }
}

const store = redux.createStore(reducer);


store.subscribe(() => {
  console.log("counter:", store.getState().counter);
});



// actions
const action1 = {
  type: "INCRMENT"
}
const action2 = {
  type: "DECRMENT"
}
const action3 = {
  type: "Add_NUM",
  num: 5
}
const action4 = {
  type: "SUB_NUM",
  num: 5
}

store.dispatch(action1)
store.dispatch(action2)
store.dispatch(action3)
store.dispatch(action4)

10.3redux的三大原则

1.单一数据源
  • 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中
  • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护;
  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改;
2.State是只读的
  • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
  • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state
  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题
3.使用纯函数来执行修改
  • Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state
  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
  • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

10.4redux测试项目搭建

安装:yarn add redux

使用过程:

  1. 创建一个对象,作为我们保存的一些状态
  2. 创建store来存储这个state

    • 创建时必须定义reducer
    • 可以通过 store.getState 来获取当前的state
  3. 通过action来修改state

    • dispatch来派发action
    • 通常action中都有type属性,也可也携带其他属性
  4. 编写reducer中的处理代码

    • reducer是一个纯函数,不需要直接修改state;
  5. 在派发之前可以通过store.subscribe(fn) 来监听state的变化

结构划分:

React中redux的使用(11)

//代码在上面这个目录里面

10.5redux融入react

1.默认写法:

App.js:

React中redux的使用(11)

Home.js:

React中redux的使用(11)

About.js:

React中redux的使用(11)

store文件:

React中redux的使用(11)

,如果是上面那样来写,假如再添加一个类似页面,多处写法都差不多,咱说不够优雅:

React中redux的使用(11)

把它们都抽离到一个函数里,函数是需要参数的,就可以把它们不相同的地方state/dispatch传过去。

2.改进:把相同代码抽离到一个文件

把所有代码放到connect.js文件里:utils/connect

connect.js

React中redux的使用(11)

About.js

React中redux的使用(11)

理解connect这个函数所做的操作,它本身是一个函数返回的又是一个高阶函数(组件),和它传入的两个参数是回调函数,一定要多写几遍。

3.再改进:更加独立的connect

如果要把connect文件打包发布到npm共所有人使用,目前来说是做不到的,因为它里面依赖了store,如果别人要使用还需到connect文件里做操作,这样看来connect并不够独立。

使用createContext:

React中redux的使用(11)

把context供所有组件使用:

React中redux的使用(11)

在这里把store传进去了,里面就可以在context里得到传过去的值。

React中redux的使用(11)

10.6使用react-redux

安装:yarn add react-redux

在需要用的组件引入里面的 connect :import { connect } from 'react-redux';

用法更上面的抽离的connect一样。

React中redux的使用(11)

React中redux的使用(11)

Provider里面需要传入store,实际上它内部传的还是value。

10.7简单阅读react-redux源码

10.8组件中的异步操作

之前的操作中,redux里保存的counter是一个本地定义的数据

  • 我们可以直接通过同步的操作来dispatch action,state就会被立即更新。
  • 但是真实开发中,redux中保存的很多数据可能来自服务器,我们需要进行异步的请求,再将数据保存到redux中

在之前学习网络请求的时候我们讲过,网络请求可以在class组件的componentDidMount中发送,所以我们可以有这样的结构:React中redux的使用(11)

React中redux的使用(11)

React中redux的使用(11)

注意:现在我们的状态都是写在initialState对象里面

上面的代码有一个缺陷:

  • 我们必须将网络请求的异步代码放到组件的生命周期中来完成
  • 事实上,网络请求到的数据也属于我们状态管理的一部分,更好的一种方式应该是将其也交给redux来管理
  • React中redux的使用(11)

如何在redux中进行异步操作?

  • 答案就是使用中间件(Middleware);
  • 学习过Express或Koa框架的童鞋对中间件的概念一定不陌生;
  • 在这类框架中,Middleware可以帮助我们在请求和响应之间嵌入一些操作的代码,比如cookie解析、日志记录、文件压缩等操作;
1.理解中间件

redux也引入了中间件(Middleware)的概念:

  • 这个中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码;
  • 比如日志记录、调用异步接口、添加代码调试功能等等;

我们现在要做的事情就是发送异步的网络请求,所以我们可以添加对应的中间件:

  • 这里官网推荐的、包括演示的网络请求的中间件是使用 redux-thunk;

redux-thunk是如何做到让我们可以发送异步的请求呢?

  • 我们知道,默认情况下的dispatch(action),action需要是一个JavaScript的对象
  • redux-thunk可以让dispatch(action函数),action可以是一个函数
  • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数

    • dispatch函数用于我们之后再次派发action;
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;
2.使用redux-thunk

安装:yarn add redux-thunk

1.在redux中引入applyMiddleware

2.应用安装的redux-thunk

  • 通过applyMiddleware可以应用多个Middleware,返回一个enhancer
  • 把返回的enhancer作为第二个参数传入createStore里

3.定义一个返回函数的action

store/index.js

React中redux的使用(11)

About.js

React中redux的使用(11)

ActionCreator.js

React中redux的使用(11)

10.9 React-devtools

开头说过redux提供了一个我们可预测的状态管理, redux可以方便的让我们对状态进行跟踪和调试,那么如何做到呢?

  • redux官网为我们提供了redux-devtools的工具;
  • 利用这个工具,我们可以知道每次状态是如何被修改的,修改前后的状态变化等等;

1.安装:

  • 在对用的浏览器安装相关的插件,如Chrome浏览器在扩展商店搜索Redux-devtools即可,或者在GitHub搜索Redux-devtools进入扩展商店。

2.使用:

  • React中redux的使用(11)

3.然后就可以在控制台使用了:

  • React中redux的使用(11)

10.10reudx-saga

sage的中间件使用了ES6的generator语法。简单了解一下generator:

1.generator

generator的基本使用:

<script>
    // 1.普通函数
    // function gen() {

    // }
    // gen();

    // 2.生成器函数  它返回的是一个generator对象
    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }

    // 调用一个生成器函数并不会直接返回结果,而是返回一个生成器对象我们称为iterator(迭代器)
    let ret = gen(); 
    console.log(ret.next());
    // 3.调用一次next,就会消耗一次迭代器,同时它返回一个对象里面包含value和done(value的值就是当前的返回值,done是一个布尔值表示是否执行完毕)
    console.log(ret.next());
    console.log(ret.next());

    // 4.生成器函数中代码的执行顺序

    // 5.练习:定义一个生成器函数,依次可以生成1-10的数字
    // function* generatorNumber() {
    //   for (let i = 1; i <= 10; i++) {
    //     yield i;
    //   }
    // }
    // let ret = generatorNumber();
    // console.log(ret.next().value);
    // console.log(ret.next().value);
    // console.log(ret.next().value); //....

    // 6.generator和promise一起使用
    function* foo() {
      // console.log('a');
      const ret = yield new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("Hello Promise");
        }, 3000);
      })
      console.log(ret);
    }
    let ret = foo();
    ret.next().value.then(ret => {
      console.log(ret);
    }) // 这里value值既然是个promise那么就可以调用它的then方法
</script>

React中redux的使用(11)

2.使用redux-sage

redux-sage是另一个比较常用在redux中发送异步请求的中间件,它的使用相对redux-thunk来说更加的灵活。

1.安装redux-sage:

yarn add redux-saga

2.集成redux-saga中间件

  • 1.导入创建的中间件函数;
  • 2.创建导入的中间件函数,放入到applyMiddleware函数中;
  • 3.启动中间件的监听过程,并且传入生成器函数
  • React中redux的使用(11)

3.saga.js

React中redux的使用(11)

  • takeEvery:可以传入多个监听的actinType,每一个都可以被执行(对应有一个takeLatest,只能执行一个,会取消前面的)
  • put:在saga中派发action不再是通过dispatch,而是通过put;
  • all:可以在yield的时候put多个action;

理一理思路:

1.首先导入saga函数,应用到middleware,然后启动saga并且需要传入一个生成器函数,

2.新建saga.js 来存放生成器函数,然后再这个文件里创建一个生成器函数把它导出,saga启动监听的生成器函数就是它。

3.在里面的生成器函数中使用takeEvery来监听actionType,并且做出相应的操作。

3.一些redux-saga原理

后面再看,在17集的80分钟左右开始讲。

10.11 Reducer拆分

1.为什么叫做reducer?

先来了解一下为什么这个函数叫做reducer?

其实在官方文档中已有说明:所以将reducer函数称之为reducer,因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue)里的回调函数属于相同的类型。

可以看出来不管是它们的参数和功能都是非常的相似。

2.拆分

我们来看一下目前我们的reducer:

  • 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
  • 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
  • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;
  • 如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护

因此,我们可以对reducer进行拆分:

  • 我们先抽取一个对counter处理的reducer;
  • 再抽取一个对home处理的reducer;
  • 将它们合并起来;

暂时对它做一个这样的拆分:

React中redux的使用(11)

每个状态都有它们自己的action、reducer、等等

home:

React中redux的使用(11)

React中redux的使用(11)

其实就是把自己的状态都抽到单独的文件夹中,最后把它们导出,放到主reducer中合并。

combineReducers是如何实现的呢?:

  • 事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
  • 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state;
  • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;

具体可以查看源码来学习。

3.React中state的管理

目前来说我们主要学习了以下三种:

  1. 组件内部state;
  2. Context数据的共享状态
  3. Redux管理应用状态。
  • 当state需要被多组件或多页面共享,我们就需要在路由发生改变的时候持久化一些data(这些应该放在redux store中)

其实state的怎样管理并没有一个标准的答案,选择怎样的状态管理,需要找一个比较平衡的方式。

目前比较好的方式是:

  1. UI相关的组件内部可以维护的状态,在组件内部自己来维护
  2. 大部分需要共享的状态,都交给redux来管理和维护
  3. 从服务器请求得数据(包括请求的操作-异步thunk,saga),交给redux来维护
  4. ..

10.12 补充一个概念:单向数据流

  1. react官网中有提到单向数据流

    • 指的是通过props进行数据的传递
  2. Vue和React中组件内部都有单向数据流的概念
  3. Redux中

React中redux的使用(11)

形容像水流一样。只能往下传递,不能颠倒。