先祭上本文的思惟导图:html
在项目中用 Redux 的时候,有时候就以为会用,可是不明白为何这样用。致使在 debug 的时候,没法快速的 debug 出缘由。并且 Redux 的源码也不复杂,暴露出来的只有 5 个 API,能够做为很好的阅读源码的开端,因此在这里很开心能够和你们一块儿来探索 Redux。若是有些讲的不许确的地方,欢迎你们提出来;也特别但愿你们积极的讨论,迸发出更多想法。react
要了解 Redux,就要从 Flux 提及。能够认为 Redux 是 Flux 思想的一种实现。那 Redux 是为何被提出来呢?就要提一下 MVC 了。git
说到 Flux,咱们就不得不要提一下 MVC 框架。github
MVC 框架将应用分为 3 个部分:redux
用户请求先到达 Controller,而后 Controller 调用 Model 得到数据,再把数据交给 View。这个想法是很理想的想法。在实际的框架应用中,大部分都是容许 View 和 Model 直接通讯的。当项目变的愈来愈大的时候,这种不一样模块之间的依赖关系就变得“不可预测”了,因此就变成了下面这样子。性能优化
虽然这张图有夸大的嫌疑,可是也说明了 MVC 在大型项目下,容易形成数据混乱的问题。架构
因此,Flux 诞生了。在写这篇文章以前,我查阅不少资料,有些说 Flux 思想替代了 MVC 框架,我则不这么认为。我的以为,Flux 思想更严格的控制了 MVC 的数据流走向。下面我们来看看 Flux 是如何严格控制数据流的。框架
一个 Flux 应用包含四个部分:ide
经过上图能够看出来,Flux 的特色就是单向数据流:函数
因此在 Flux 体系下,若是想要驱动界面,只能派发一个 Store,别无他法。在这种规矩下,若是想要追溯一个应用的逻辑就变得很轻松了。并且这种思想解决了 MVC 中没法杜绝 View 和 Model 之间的直接对话的问题。
这里就不具体讲关于 Flux 的例子了,若是想要更了解 Flux ,能够看一下阮一峰老师的 Flux 架构入门教程。
Redux 是 Flux 的一种实现,意思就是除了“单向数据流”以外,Redux 还强调三个基本原则:
在 Flux 中,应用能够拥有多个 Store,可是分红多个 Store 容易形成数据冗余,数据一致性不太好处理,并且 Store 之间可能还会有依赖,增长了应用的复杂度。因此 Redux 对这个问题的解决方法就是:整个应用只有一个 Store。
就是不能直接修改状态。若是想要修改状态,只能经过派发一个 Action 对象来完成。
这里说的纯函数就是 Reducer。按照 redux 做者 Dan 的说法:Redux = Reducer + Flux
。
下面我们根据例子来了解一下 Reudx 在 React 中的应用。
建立一个 Redux 应用须要下面几部分:
他们分别是什么意思呢?下面咱们来举一个例子: 好比下面是商场某品牌鞋子的展现柜:
店长来视察,发现鞋子2
放的过高了,并且这款鞋仍是店里的主推款,放在这个位置不适合宣传,就让店员把鞋子 2 往下挪两排
,放下去以后,店长看着舒服多了。
其实经过上面的例子,咱们如今就很好解释 Redux 了:
因此整个过程能够是下面这样:
Store
决定了 View
,而后用户的交互产生了 Action
,Reducer
根据接收到的 Action
执行任务,从而改变 Store
中的 state
,最后展现到 View
上。那么,Reducer
如何接收到动做(Action
)信号的呢?伴随着这个问题,我们来看一个例子。
了解了 Redux 中各个部分表明的意思,下面我们来经过一个计数器的例子进一步了解一下 Redux 的原理(具体代码能够看 GitHub)。咱们想要的最终效果以下:
根据上面的思路,能够分别把 Action 和 Reducer 定义为:
那么咱们来建立 Action 和 Reducer 这两个文件:
首先咱们建立一个 ActionTypes.js
和 Actions.js
这两个文件。ActionType 表明的就是 Action 的类型,能够看到它是一个常量。在 Actions.js
中,咱们定义了两个 Action 构造函数,他们返回的都是一个简单对象 (plain object
),并且每一个对象必须包含 type 属性。
能够看出来 Action 明确表达了咱们想要作的事情(加和减)。
可能有些同窗会问,在 Action 中,有时候也会 return 一个 function,不是简单对象。其实这个时候,是中间件拦截了 Action,若是是 function,就执行中间件中的方法。可是我们此次不讲中间件,因此就先忽略这种状况。
能够看到 Reducer 是一个纯函数。它接收两个参数 state 和 Action,根据接收到的 state 和 Action 来判断本身须要对当前的 state 作哪些操做,而且返回新的 state。
在 Reducer 中咱们给了 state 一个默认的值,这就是咱们的初始 state。关于 Redux 是如何返回初始值的,继续往下看。
Action 和 Reducer 都有了,那怎么让他们两个联系起来呢?下面我们看一下 Redux 中的精华部分 - Store
。
首先咱们先建立 Store:
在 store.js
中,咱们把 reducer 传给 createStore
方法而且执行了它,来建立 Store。这个方法是 Redux 的精髓所在。
下面看一下 createStore
的源码部分:
createStore 接收三个参数:
reducer{Function}
state{any}
(可选参数)enhancer{Function}
(可选参数)返回一个对象,这个对象包含五个方法,我们目前先只关注前三个方法:
在整个 createStore
中,只执行了 dispatch({ type: ActionTypes.INIT })
这一句代码。那 dispatch 作了什么呢?
我省略了一些代码,这是 dispatch 方法的核心代码。它接收一个 action 对象,而且把 createStore
接收到的 state 参数和经过 dispatch 方法传进来的 Action 参数,传给了 Reducer 而且执行,而后把 reducer 返回的 state 赋值给 currentState
。最后执行订阅队列中的方法。
createStore 方法一上来就执行了 dispatch({ type: ActionTypes.INIT })
。这句话的意思我们如今也清楚了,它的主要目的就是初始化 state。
如今我们已经把 Action 和 Reducer 联系起来了。能够看到,在 createStore
方法中,它维护一个变量 currentState
,经过 dispatch 方法来更新 currentState
变量。外部若是想要获取 currentState
,只须要调用 createStore 暴露出来的 getState
方法便可:
getState 方法是获取当前的 currentState
变量,若是想要实时获取 state,那就须要注册监听事件,每次 dispatch 的时候,就都会执行一遍这个事件。
如今我们来梳理一下思路:
Action
:这次动做的目的Reducer
:根据接收到的 Action 命令来作具体的操做Store
:把 Action 传给 Reducer,而且更新 state。而后执行订阅队列中的方法。Redux 和 React 是两个独立的产品,可是若是两个结合使用,就不得不提 react-redux
这个库了,能够大大的简化代码的书写,可是我们先不讲这个库,来本身实现一下。
你们都知道,在 React 中咱们都是使用 props 来传递数据的。整个 React 应用就是一个组件树,一层一层的往下传递数据。
可是若是在一个多层嵌套的组件结构中,只有最里层的组件才须要使用这个数据,致使中间的组件都须要帮忙传递这个数据,咱们就要写不少次 props,这样就很麻烦。
好在 React 提供了一个叫作 context
的功能,能够很好的解决和这个问题。
所谓 context 就是“上下文环境”,让一个树状组件上全部组件都能访问一个共同的对象,为了完成这个任务,须要上下级组件的配合。
首先是上级组件宣称本身支持 context,而且提供给一个函数来返回表明 context 的对象。
而后,子组件只要宣称本身须要这个 context,就能够经过 this.context
来访问这个共同的对象。
因此咱们能够利用 React 的 context,把 Store 挂在它上面,就能够实现全局共享 Store 了。
了解了如何在 React 中共享 Store,那我们就动手来实现一下吧~
Provider
,顾名思义,它是提供者,在这个例子中,它是 context 的提供者。
就像下面这样来使用:
Provider
提供了一个函数 getChildContext
,这个函数返回的是就是表明 context 的对象。在调用 Store 的时候能够从 context 中获取:this.context.store
。
Provider
为了声明本身是 context 的提供者,还须要指定 Provider
的 childContextTypes
属性(须要和 getChildContext
对其)。
只有具有上面两个特色,Provider
才有可能访问到 context。
好了,Provider
组件我们已经完成了,下面我们就能够把 context 挂到整个应用的顶层组件上了。
进入整个应用的入口文件 index.js:
咱们把 Store 做为 props 传递给了 Provider
组件,Provider 组件把 Store 挂在了 context 上。因此下面咱们就要从 context 中来获取 Store 了。
下面是咱们整个计数器应用的骨架部分。
咱们先把页面渲染出来:
在上面的组件中,咱们作了两件事情:
如何声称本身须要 context 呢?
contextTyp
e 赋值,值的类型和 Provider 中提供的 context 的类型同样。this.context
来调用 context 了。getState
方法。上面咱们了解过,getState 方法返回的就是 createStore 方法中维护的那个变量。在 createStore
执行的时候,就已经初始化过了这个变量。
接下来咱们给“加号”加上具体动做。
咱们想要把数字加一,因此就有一个“加”的动做,这个动做就是一个 Action,这个 Action 就是 addAction
。若是想要触发这个动做,就须要执行 dispatch 方法。
经过 dispatch 方法,把 Action 对象传给了 Reducer,通过处理,Reducer 会返回一个加 1 的新 state。
其实如今 Store 中的数据已是最新的了,能够咱们看到页面上尚未更新。那咱们如何能获取到最新的 state 呢?
就像关注公众号同样,我只须要在最开始的时候订阅一下,以后每次有更新,我都会收到推送。
这个时候就要使用 Store 的 subscribe 方法了。顾名思义,就是我要订阅 state 的变化。咱们先看一下代码怎么写:
在组件的 componentDidMount
生命周期中,咱们调用了 store 的 subscribe
方法,每次 state 更新的时候,都会去调用 onChange
方法;在 onChange
方法中,咱们会取得最新的 state,而且赋值。在组件被卸载的时候,咱们取消订阅。
上面这样就完成了订阅功能。这时候再运行程序,能够发现页面上就会显示最新的数字了。
在这个例子中,能够看出来咱们能够抽象出来不少逻辑,好比 Provider
,还有订阅 store 变化的功能。其实这些 react-redux 都已经帮咱们作好了。
在我们这个例子中,只是简单的实现了一下 react-redux
部分功能。具体的你们能够到官网上去看。
下面我们来总结一下 redux 和 react 结合使用的整个数据流:
good~ 咱们已经所有完成了整个应用。如今你们了解 Redux 的运行原理 了吗?
具体代码能够到 GitHub 查看。
参考资料:
本文永久连接: