【React系列】从零开始入门Redux

900 阅读10分钟

       我在学习redux的时候,一开始也觉得很难,很多东西很难以理解,因为我是先简单学习了一遍Vue的,因为工作中用的都是react,所以Vue也忘得差不多了。写这篇文章的原因是想帮助想入门react的小伙伴们,同时也是对自己的一个技术小总结吧。

       react难就难在它很自由,就提供了一个JSX,然后就没有了。不过和当初React的定位也有关系,它并不是一个框架,而是一个UI库。而Vue才是一个框架,因为很多事情它都帮你完成了,使用的时候仅需要知道相关的API。

       tips:可以这么粗浅的理解react,UI = fn(data)

好了扯远了,下面开始带你入门redux吧。

redux

       首先,在学习之前,要说明一点,react和redux没有任何关系,没有任何关系,没有任何关系;重要的事情说三遍。那redux是什么呢?是一个管理数据的仓库。

为什么需要redux呢?

      原因是因为前端数据的复杂度没有人来管理,因为react当初的定位也是为了解决中大型项目,项目一旦复杂起来,需要共享的数据就会很庞大复杂。

redux的核心概念

      在介绍核心概念之前,我们先来了解一个东西,就是Flux。在Flux中,Facebook最早提出了一个action的概念,通过action来直接修改数据仓库store,action是一个普通的对象,用于描述要干什么。store根据不同的action来对数据进行操作。

      这样就会有一个问题,当业务复杂,数据庞大的时候,store的压力就很多了,所以才有了后面redux的出现,到这里,你只需要知道redux的三个核心概念就可以了。

      action:相当于页面请求等操作,分发dispatch

      reducer:数据的控制器,数据的修改者,就是修改数据的

      store:数据的仓库

redux是如何工作的?


第一步:首先action是一个平面对象,就是一个简单的对象,必须要传type属性,类型任意,代表着你要干什么,还有一个可选属性是payload,就是请求时候的附加数据。

第二步:store接收到action的请求之后,把旧的数据state,和请求action交给reducer,让reducer来修改数据,修改完之后返回一个新的数据newState给我,以便更新数据。

       明白了上图描述的redux工作流程之后,我们需要创建一个store,因为根据上图,不管是接收action,或者是触发reducer修改数据,都是根据数据仓库的,所有我们先要创建一个数据仓库。直接看下面代码和注释吧。

createStore

       createStore 返回值是一个对象,对象包含以下几个属性

       dispatch:分发一个action,自然要接收一个action参数

       getState:得到当前的state数据

       subscribe:订阅一个监听器,分发action的时候自动会执行,订阅时候返回一个函数,方便取消订阅。

       Symbol('observable'):提案,暂时用不到

tips:到这里,熟悉发布订阅模式的小伙伴,就知道其实 createStore 内部的代码原理就是使用了发布订阅模式。不熟悉发布订阅模式的小伙伴需要去了解一下。

代码


接着我们把action和reducer完善好


最后我们就可以使用了,结合上图的两个步骤理解


tips:最好自己手动实现一遍代码,然后按照redux工作原理两大步骤图理解一下。

bindActionCreators

       根据上面redux的工作流程,有没有发现太麻烦了?的确是太麻烦了,尤其是第二步,要自己主动去使用store.dispatch,然后再往里面塞action的平面对象。那有没有方便一点的方法呢?redux提供了一个函数,bindActionCreators,增强action创建函数,让action之后自动触发dispatch。

/**
* bindActionCreators,增强action,自动dispach。
* 接受两个参数
* 第一个是actions,如果是函数,返回一个函数,如果是对象,返回一个对象
* 第二个dispach,actions需要增强的函数
*/

代码


使用起来就相当简单了,下面做一个对比


combineReducers

       现在还有一个问题,就是仓库数据只有一个,只有一个reducer,而在实际的项目中,数据是很庞大的,有很多个reducer,那怎么来管理这么多数据呢?


代码

我们可以先这样解决问题


其实redux很贴心,为我么提供了相当于上面效果的一个函数,就是 combineReducers。

/**
* 组装reducers, 返回一个reducer,数据使用一个对象表示,
* 对象的属性名与传递的参数对象保持一致
* 返回的是一个总的 reducer函数
*/


完善 validiteReducers 函数


使用的时候,只需要简单的导出 combineReducers 的运行结果即可,因为它返回一个总的reducer函数,可以对比一下不使用combineReducers的时候。


middleware

       现在我们基本上算是完成了一个简陋版的redux,但是在实际的业务开发中,往往需要请求数据,打印日志之类的很多附加功能,我们就举一个简单的例子。现在有一个需求,我要打印旧数据和新数据。首先我们分析一下需求,结合redux的工作流程,我们知道,action必须返回一个没有副作用的平面对象,reducer必须是一个纯函数。况且action只能拿到旧数据,reducer也不行,观察发现createStore里面的可以拿,那只能在里面做手脚了。怎么做手脚呢?又不能影响原来的功能。

代码

机智的我们可以先这样做


其实这个就是middleware中间件的原理。而且,中间件如何运用在redux上?总不能像上面这么low的写法吧?别急,我们一步步慢慢分析,理清楚下面这些概念

/**
* 什么是中间件?
* 中间件,像插件,可以不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。
* 在redux中,主要是增强dispatch函数
*
* 原理:更改仓库中的dispatch函数
* 1.先用一个变量保存原来的引用,然后完全改写了原来的函数。
* 2.在新改写的函数中增强我们需要的功能之后再运行一下原来的函数。
* 3.这样就达到了目的了
*
* 如何书写?
* 1.中间件本身是一个函数,该函数接受一个store参数,表示创建的仓库
* 2.该仓库并非一个完成的仓库,仅包含getState, dispatch
* 3.该函数等仓库创建完运行
* 4.由于创建仓库后需要自动运行设置的中间件函数,因此需要在创建仓库时,告诉仓库有哪些*    中间件
* 5.需要调用applyMiddleware函数,将函数的返回值作为createStore的第二或第三参数
*
* 6.中间件函数必须返回一个函数,用来创建dispatch函数
*/

先看第五点,要使用中间件,必须调用applyMiddleware函数,将函数的返回值作为createStore的第二或第三参数,来告诉仓库,我们创建仓库的时候,要使用中间件。假设我们有下面三个中间件,创建仓库的时候调用applyMiddleware函数。applyMiddleware函数会倒着来运行每个中间件。在这里你可以先不管它为什么倒着来,先忽略。


然后用代码来写一遍,先书写三个中间件。


然后使用中间件


applyMiddleware

到这里,你可能会问applyMiddleware为什么要倒着来运行所有的中间件,这和代码的运行逻辑有关,下面我们就来解析一下applyMiddleware是怎么来把所有的中间件运行起来的。在介绍applyMiddleware前,我们先来认识一下compose,它是函数组合, 函数式编程中的声明式编程,其实就是把所有的函数的功能组合到一起。

compose

其实可以简单的理解成,把所有函数的功能组合成一个函数,就是compose。


有了compose之后,我们就可以实现applyMiddleware的原理了,如下代码


到这里,我们就知道为什么applyMiddleware一开始调用的时候是反过来的了。compose函数式编程的魔法,真香!!!

/**
* 为什么applyMiddleware一开始调用的时候是反过来的呢?
* 因为applyMiddleware之后,dispatch才会运行,这时候才是正向运行,
* 如果一开始不倒着来,得到的结果是倒着的喔!!!
* 在中间件中,如果不调用next,就不会调用下一个中间件。
* 这样的好处就是自己完成自己的事件了,不管你有几个中间件。
*/

至此,三个中间件就会运用在创建函数里面了,这也是为什么创建函数的第二个参数可以传中间件或者默认值,createStore的完整代码应该是这样的。

只看上面的代码肯定很羞涩难懂,结合下面的图片看才好理解


其实redux用applyMiddleware使用中间件就是一个洋葱模型,用到切洋葱(redux)的时候真想哭(学redux),吃洋葱(react+redux)的时候,真香。


redux-thunk【扩展】

我们在写业务代码的时候,常常需要AJAX请求数据回来才渲染界面,如果用redux请求数据,我又坚持要放在action呢?该怎么请求呢?我们知道,action必须返回一个没有副作用的平面对象,不行,怎么办呢?redux-thunk 中间件就是专门处理带有副作用的action的

/**

* redux-thunk 中间件,处理副作用, 一般放在第一个。(后面解释)
* 怎么处理副作用呢?他是处理action的副作用的。
* action 必须是一个平面的对象,没有任何的副作用,但是有了thunk之后,
* 它可以让 action 变成一个函数,允许有副作用。当 action 是一个函数被分发时,
* thunk 会阻止 action 继续向后移交。要继续dispatch才往后移交,
* 这样的话就可以在里面处理很多副作用了,比如AJAX请求等等。
* thunk会向函数中传三个参数:
* dispatch:store.dispatch (为了重新走一次流程)
* getState: store.dispatch (可以获取数据)
* extra:用户设置的额外参数 thunk.withExtraArgument(123), 使用中间件的时候配置下去。
*/

代码


那为什么要放在第一个呢?


tips:再仔细想下整个流程,最后跟着思路码一边。

thunk源码


connect

前面我们都是在讲redux怎么管理数据,那我们的组件怎么使用数据呢?

假设我们都定义好了action、reducer,并且创建好了store。

那么接下来我们可以这样拿到数据(Users组件拿),直接定义两个方法,把需要的数据映射进去。


仔细看mapStateToPropsmapDispatchToProps。我们把需要的数据直接通过导入store,传进去就完事了。同时订阅一下this.setState就可以完成改变数据就刷新界面的效果。

redux还是很贴心的,哪里麻烦就出哪里的API,这时候就可以使用react-redux中的 connect 方法了。首先你要理解下面的笔记,或者可以直接去看相关的API文档。


有了connect之后代码就可以变得很简单了。直接导出就完事了


总结

       redux其实使用不难,但是理解原理,解析原理代码是非常绕的,一定要多写几遍代码理解才能真的学会使用redux。

       这里提供我学习redux源码的所有代码笔记,有兴趣的小伙伴可以clone下来研究学习。

github:github.com/huangruitia…