经过了解 Redux 简单源码,掌握 Redux 数据流原理

先祭上本文的思惟导图:html

1、为何讲 Redux

在项目中用 Redux 的时候,有时候就以为会用,可是不明白为何这样用。致使在 debug 的时候,没法快速的 debug 出缘由。并且 Redux 的源码也不复杂,暴露出来的只有 5 个 API,能够做为很好的阅读源码的开端,因此在这里很开心能够和你们一块儿来探索 Redux。若是有些讲的不许确的地方,欢迎你们提出来;也特别但愿你们积极的讨论,迸发出更多想法。react

2、Redux 为何会出现

要了解 Redux,就要从 Flux 提及。能够认为 Redux 是 Flux 思想的一种实现。那 Redux 是为何被提出来呢?就要提一下 MVC 了。git

一、MVC

说到 Flux,咱们就不得不要提一下 MVC 框架。github

MVC 框架将应用分为 3 个部分:redux

  • View:视图,展现用户界面
  • Controller:管理应用的行为和数据,响应用户的输入(常常来自 View)和更新状态的指令(常常来自 Model)
  • Model:管理数据,大部分业务逻辑也在 Model 中

用户请求先到达 Controller,而后 Controller 调用 Model 得到数据,再把数据交给 View。这个想法是很理想的想法。在实际的框架应用中,大部分都是容许 View 和 Model 直接通讯的。当项目变的愈来愈大的时候,这种不一样模块之间的依赖关系就变得“不可预测”了,因此就变成了下面这样子。性能优化

虽然这张图有夸大的嫌疑,可是也说明了 MVC 在大型项目下,容易形成数据混乱的问题。架构

因此,Flux 诞生了。在写这篇文章以前,我查阅不少资料,有些说 Flux 思想替代了 MVC 框架,我则不这么认为。我的以为,Flux 思想更严格的控制了 MVC 的数据流走向。下面我们来看看 Flux 是如何严格控制数据流的。框架

二、Flux

一个 Flux 应用包含四个部分:ide

  • Dispatcher,处理动做分发,维持 Store 之间的依赖关系
  • Store,负责存储数据和处理数据相关逻辑
  • Action,触发 Dispatcher
  • View,视图,负责显示用户界面

经过上图能够看出来,Flux 的特色就是单向数据流函数

  • 用户在 View 层发起一个 Action 对象给 Dispatcher
  • Dispatcher 接收到 Action 并要求 Store 作相应的更新
  • Store 作出相对应更新,而后发出一个 change 事件
  • View 接收到 change 事件后,更新页面

因此在 Flux 体系下,若是想要驱动界面,只能派发一个 Store,别无他法。在这种规矩下,若是想要追溯一个应用的逻辑就变得很轻松了。并且这种思想解决了 MVC 中没法杜绝 View 和 Model 之间的直接对话的问题。

这里就不具体讲关于 Flux 的例子了,若是想要更了解 Flux ,能够看一下阮一峰老师的 Flux 架构入门教程

四、Redux 诞生

Redux 是 Flux 的一种实现,意思就是除了“单向数据流”以外,Redux 还强调三个基本原则:

  • 惟一的 store(Single Source of Truth)
  • 保持状态只读(State is read-only)
  • 数据改变只能经过纯函数完成(Changes are made with pure functions)

a. 惟一的 store

在 Flux 中,应用能够拥有多个 Store,可是分红多个 Store 容易形成数据冗余,数据一致性不太好处理,并且 Store 之间可能还会有依赖,增长了应用的复杂度。因此 Redux 对这个问题的解决方法就是:整个应用只有一个 Store。

b. 保持状态只读

就是不能直接修改状态。若是想要修改状态,只能经过派发一个 Action 对象来完成。

c. 数据改变只能经过纯函数完成

这里说的纯函数就是 Reducer。按照 redux 做者 Dan 的说法:Redux = Reducer + Flux

3、在 React 中应用 Redux

下面我们根据例子来了解一下 Reudx 在 React 中的应用。

一、Redux 中的数据流动

建立一个 Redux 应用须要下面几部分:

  • Actions
  • Reducers
  • Store

他们分别是什么意思呢?下面咱们来举一个例子: 好比下面是商场某品牌鞋子的展现柜:

鞋子展现柜

店长来视察,发现鞋子2放的过高了,并且这款鞋仍是店里的主推款,放在这个位置不适合宣传,就让店员把鞋子 2 往下挪两排,放下去以后,店长看着舒服多了。

鞋子展现柜

其实经过上面的例子,咱们如今就很好解释 Redux 了:

  • View: 鞋子摆放在鞋架上的总体效果
  • Action: 店长给店员分配的任务(往下挪鞋子)
  • Reducers: 具体任务的实施者(把鞋子往下挪两排)
  • Store: 鞋子在鞋架上的具体位置

因此整个过程能够是下面这样:

Store 决定了 View,而后用户的交互产生了 ActionReducer 根据接收到的 Action 执行任务,从而改变 Store 中的 state,最后展现到 View 上。那么,Reducer 如何接收到动做(Action)信号的呢?伴随着这个问题,我们来看一个例子。

二、Redux 实践

了解了 Redux 中各个部分表明的意思,下面我们来经过一个计数器的例子进一步了解一下 Redux 的原理(具体代码能够看 GitHub)。咱们想要的最终效果以下:

根据上面的思路,能够分别把 Action 和 Reducer 定义为:

  • 动做(Action): 加
  • 执行者(Reducer): 加 1

那么咱们来建立 Action 和 Reducer 这两个文件:

Actions

首先咱们建立一个 ActionTypes.jsActions.js 这两个文件。ActionType 表明的就是 Action 的类型,能够看到它是一个常量。在 Actions.js 中,咱们定义了两个 Action 构造函数,他们返回的都是一个简单对象 (plain object),并且每一个对象必须包含 type 属性。

能够看出来 Action 明确表达了咱们想要作的事情(加和减)。

可能有些同窗会问,在 Action 中,有时候也会 return 一个 function,不是简单对象。其实这个时候,是中间件拦截了 Action,若是是 function,就执行中间件中的方法。可是我们此次不讲中间件,因此就先忽略这种状况。

Reducer

能够看到 Reducer 是一个纯函数。它接收两个参数 state 和 Action,根据接收到的 state 和 Action 来判断本身须要对当前的 state 作哪些操做,而且返回新的 state。

在 Reducer 中咱们给了 state 一个默认的值,这就是咱们的初始 state。关于 Redux 是如何返回初始值的,继续往下看。

Action 和 Reducer 都有了,那怎么让他们两个联系起来呢?下面我们看一下 Redux 中的精华部分 - Store

createStore

首先咱们先建立 Store:

store.js 中,咱们把 reducer 传给 createStore 方法而且执行了它,来建立 Store。这个方法是 Redux 的精髓所在。

下面看一下 createStore 的源码部分:

createStore 接收三个参数:

  • reducer{Function}
  • state{any}(可选参数)
  • enhancer{Function}(可选参数)

返回一个对象,这个对象包含五个方法,我们目前先只关注前三个方法:

  • dispatch
  • subscribe
  • getState

在整个 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 这个库了,能够大大的简化代码的书写,可是我们先不讲这个库,来本身实现一下。

二、store 和 context 结合

你们都知道,在 React 中咱们都是使用 props 来传递数据的。整个 React 应用就是一个组件树,一层一层的往下传递数据。

可是若是在一个多层嵌套的组件结构中,只有最里层的组件才须要使用这个数据,致使中间的组件都须要帮忙传递这个数据,咱们就要写不少次 props,这样就很麻烦。

好在 React 提供了一个叫作 context 的功能,能够很好的解决和这个问题。

所谓 context 就是“上下文环境”,让一个树状组件上全部组件都能访问一个共同的对象,为了完成这个任务,须要上下级组件的配合。

首先是上级组件宣称本身支持 context,而且提供给一个函数来返回表明 context 的对象。

而后,子组件只要宣称本身须要这个 context,就能够经过 this.context 来访问这个共同的对象。

因此咱们能够利用 React 的 context,把 Store 挂在它上面,就能够实现全局共享 Store 了。

了解了如何在 React 中共享 Store,那我们就动手来实现一下吧~

Provider

Provider,顾名思义,它是提供者,在这个例子中,它是 context 的提供者。

就像下面这样来使用:

Provider 提供了一个函数 getChildContext,这个函数返回的是就是表明 context 的对象。在调用 Store 的时候能够从 context 中获取:this.context.store

Provider 为了声明本身是 context 的提供者,还须要指定 ProviderchildContextTypes 属性(须要和 getChildContext 对其)。

只有具有上面两个特色,Provider 才有可能访问到 context。

好了,Provider 组件我们已经完成了,下面我们就能够把 context 挂到整个应用的顶层组件上了。

进入整个应用的入口文件 index.js

咱们把 Store 做为 props 传递给了 Provider 组件,Provider 组件把 Store 挂在了 context 上。因此下面咱们就要从 context 中来获取 Store 了。

消费者

下面是咱们整个计数器应用的骨架部分。

咱们先把页面渲染出来:

在上面的组件中,咱们作了两件事情:

  • 第一件事情是:声称本身须要 context
  • 第二件事情是:初始化 state。

如何声称本身须要 context 呢?

  • 首先是须要给 App 组件的 contextType 赋值,值的类型和 Provider 中提供的 context 的类型同样。
  • 而后在构造函数中加上 context,这样组件的其余部分就能够经过 this.context 来调用 context 了。
  • 而后是初始化 state。看代码能够知道,咱们调用了挂在 context 上的 Store 的 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,而且赋值。在组件被卸载的时候,咱们取消订阅。

上面这样就完成了订阅功能。这时候再运行程序,能够发现页面上就会显示最新的数字了。

react-redux

在这个例子中,能够看出来咱们能够抽象出来不少逻辑,好比 Provider,还有订阅 store 变化的功能。其实这些 react-redux 都已经帮咱们作好了。

  • Provider: 提供包含 store 的 context
  • connect: 把 state 转化为内层组件的 props,监听 state 的变化,组件性能优化

在我们这个例子中,只是简单的实现了一下 react-redux 部分功能。具体的你们能够到官网上去看。

总结

下面我们来总结一下 redux 和 react 结合使用的整个数据流:

good~ 咱们已经所有完成了整个应用。如今你们了解 Redux 的运行原理 了吗?

具体代码能够到 GitHub 查看。

参考资料:

本文永久连接

相关文章
相关标签/搜索