干货 | React技术栈耕耘 —— Redux

做者:小boy (沪江web前端开发工程师)
本文为原创文章,有不当之处欢迎指出。转载请注明出处。
文章示例代码:https://github.com/ikcamp/rea...前端

Redux 是近年来提出的 Flux 思想的一种实践方案,在它以前也有 reflux 、 fluxxor 等高质量的做品,但短短几个月就在 GitHub 上获近万 star 的成绩让这个后起之秀逐渐成为 Flux 的主流实践方案。react

正如 Redux 官方所称,React 禁止在视图层直接操做 DOM 和异步行为 ( removing both asynchrony and direct DOM manipulation ),来拆开异步和变化这一对冤家。但它依然把状态的管理交到了咱们手中。Redux 就是咱们的状态管理小管家。git

安利的话先暂时说到这,本期的技术周刊将为你带来 React-Redux 在沪江前端团队中的实践。github

0. 放弃

你没有看错,在开始以前咱们首先谈论一下什么状况下不该该用 Redux。
所谓杀鸡焉用宰牛刀,任何技术方案都有其适用场景。做为一个思想的实践方案,Redux 必然会为实现思想立规矩、铺基础,放在复杂的 React 应用里,它会是“金科玉律”,而放在结构不算复杂的应用中,它只会是“繁文缛节”。web

若是咱们将要构建的应用无需多层组件嵌套,状态变化简单,数据单一,那么就应放弃 Redux ,选用单纯的 React 库 或其余 MV* 库。毕竟,没有人愿意雇佣一个收费比本身收入还高的财务顾问。编程

1. 思路

首先,咱们回顾一下 Redux 的基本思路redux

clipboard.png

当用户与界面交互时,交互事件的回调函数会触发 ActionCreators ,它是一个函数,返回一个对象,该对象携带了用户的动做类型和修改 Model 必需的数据,这个对象也被咱们称做 Action 。数组

以 TodoList 为例,添加一个 Todo 项的 ActionCreator 函数以下所示:promise

clipboard.png

在上例中,addTodo 就是 ActionCreator 函数,该函数返回的对象就是 Action 。数据结构

其中 type 为 Redux 中约定的必填属性,它的做用稍后咱们会讲到。而 text 则是执行 “添加 Todo 项“ 这个动做必需的数据。

固然,不一样动做所须要的数据也不尽相同,如 “删除Todo” 动做,咱们就须要知道 todo 项的 id,“拉取已有的Todo项” 动做,咱们就须要传入一个元素为 Todo 项对象的数组( todos )。形如 text 、 id 、 todos 这类属性,咱们习惯称呼其为 “ payload ” 。

如今,咱们获得了一个 “栩栩如生” 的动做。它足够简洁,但担任 Model 的 store 暂时还不知道如何感知这个动做从而改变数据结构。

为了处理这个关键问题,Reducer 巧然登场。它仍然是一个函数,并且是没有反作用的纯函数。它只接收两个参数:state 和 action ,返回一个 newState 。

没错,state 就是你在 React 中熟知的 state,但根据 Redux 三原则 之一的 “单一数据源” 原则,Reducer 幽幽地说:“你的 state 被我承包了。”

因而,单一数据源规则实施起来,是规定用 React 的顶层容器组件( Container Components )的 state 来存储单一对象树,同时交给 Redux store 来管理。

这里区分一下 state 和 Redux store:state 是真正储存数据的对象树,而 Redux store 是协调 Reducer、state、Action 三者的调度中心。

而如此前所说,Reducer 此时手握两个关键信息:旧的数据结构(state),还有改变它所须要的信息 (action),而后聪明的 Reducer 算盘一敲,就能给出一个新的 state ,从而更新数据,响应用户。下面依然拿 TodoList 举例:

clipboard.png

当接收到一个 action 时,Reducer 从 action.type 识别出该动做是要添加 Todo 项,而后路由到相应的处理方案,接着根据 action.text 完成了处理,返回一个 newState 。过程之间,整个应用的 state 就从 state => newState 完成了状态的变动。

这个过程让咱们很天然地联想到去银行存取钱的经历,显然咱们应该告诉柜台操做员要存取钱,而不是遥望着银行的金库自言自语。

Reducer 为咱们梳理了全部变动 state 的方式,那么 Redux store 从无到有,从有到变都应该与 Reducer 强关联。

所以,Redux 提供了 createStore 函数,他的第一个参数就是 Reducer ,用以描绘 state 的更改方式。第二个是可选参数 initialState ,此前咱们知道,这个 initialState 参数也能够传给 Reducer 函数。放在这里作可选参数的缘由是为同构应用提供便捷。

clipboard.png

createStore 函数最终返回一个对象,也就是咱们所说的 store 对象。主要提供三个方法: getStatedispatch subscribe。 其中 getState() 得到 state 对象树。dispatch(actionCreator) 用以执行 actionCreators,建起从 action 到 store 的桥梁。

仅仅完成状态的变动可不算完,咱们还得让视图层跟上 store 的变化,因而 Redux 还为 store 设计了 subscribe 方法。顾名思义,当 store 更新时,store.subscribe() 的回调函数会更新视图层,以达到 “订阅” 的效果。

在 React 中,有 react-redux 这样的桥接库为 Redux 的融入铺平道路。因此,咱们只需为顶层容器组件外包一层 Provider 组件、再配合 connect 函数处理从 store 变动到 view 渲染的相关过程。

clipboard.png

而顶层容器组件往下的子组件只需凭借 props 就能一层层地拿到 store 数据结构的数据了。就像这样:

clipboard.png

至此,咱们走了一圈完整的数据流。然而,在实际项目中,咱们面临的需求更为复杂,与此同时,redux 和 react 又是具备强大扩展性的库,接下来咱们将结合以上的主体思路,谈谈咱们在实际开发中会遇到的一些细节问题。

2. 细节

应用目录

清晰的思路须辅以分工明确的文件模块,才能让咱们的应用达到更佳的实践效果,同时,统一的结构也便于脚手架生成模板,提升开发效率。

如下的目录结构为团队伙伴屡次探讨和改进而来(限于篇幅,这里只关注 React 应用的目录。):

clipboard.png

入口文件 app.js 与顶层组件 react/container.js

这块咱们基本上保持和以前思路上的一致,用 react-redux 桥接库提供的 Provider 与函数 connect 完成 Redux store 到 React state 的转变。

细心的你会在 Provider 的源码中发现,它最终返回的仍是子组件(本例中就是顶层容器组件 “Container“ )。星星仍是那个星星,Container 仍是那个 Container,只是多了一个 Redux store 对象。

而 Contaier 做为 业务组件 Wrapper 的 高阶组件 ,负责把 Provider 赋予它的 store 经过 store.getState() 获取数据,转而赋值给 state 。而后又根据咱们定义的 mapStateToProps 函数按必定的结构将 state 对接到 props 上。 mapStateToProps 函数咱们稍后详说。以下所见,这一步主要是 connect 函数干的活儿。

clipboard.png

clipboard.png

业务组件 component/Wrapper.js 与 mapStateToProps

这两个模块是整个应用很重要的业务模块。做为一个复杂应用,将 state 上的数据和 actionCreator 合理地分发到各个业务组件中,同时要易于维护,是开发的关键。

首先,咱们设计 mapStateToProps 函数。须要谨记一点: 拿到的参数是 connect 函数交给咱们的根 state,返回的对象是最终 this.props 的结构。

和 Redux 官方示例不一样的是,咱们为了可读性,将分发 action 的函数也囊括进这个结构中。这也是得益于 bindActions 模块,稍后咱们会讲到。

clipboard.png

这样,咱们这个函数就准备好履行它分发数据和组件行为的职责了。那么,它又该如何 “服役” 呢?

敏锐的你必定察觉到刚才咱们设计的结构中,以 “ params ” 开头的属性既没起到给组件展现数据的做用,又没有为组件发送 action 的功能。它们即是咱们分发以上两种功能属性的关键。

咱们先来看看业务组件 Wrapper :

clipboard.png

如今,param 属性们为咱们展现了它扮演的角色:在组件中实际分发数据和方法的快递小哥。这样,即便项目越变越大,组件嵌套愈来愈多,咱们也能在 param.js 模块中,清晰地看到咱们的组件结构。需求更改的时候,咱们也能快速地定位和修改,而不用对着堆积如山的组件模块梳理父子关系。

相信你应该能猜到剩下的子组件们怎么取到数据了,这里限于篇幅就不贴出它们的代码了。

Action 模块: react/action.js、react/actionType.js 和 react/bindActions.js

在前面的介绍中,咱们提到:一个 ActionCreator 长这样:

clipboard.png

而在 Redux 中,真正让其分发一个 action ,并让 store 响应该 action,依靠的是 dispatch 方法,即:

clipboard.png

交互动做一多,就会变成:

clipboard.png

而容易想到:抽象出一个公用函数来分发 action (这里粗略写一下个人思路,简化方式并不惟一)

clipboard.png

而细心的 Redux 已经为咱们提供了这个方法 —— bindActionCreator
因此,咱们的 bindActions.js 模块就借用了 bindActionCreator 来简化 action 的分发:

clipboard.png

不难想象,action 模块里就是一个个 actionCreator :

clipboard.png

为了更好地合做,咱们单独为 action 的 type 划分了一个模块

clipboard.png

react/reducers/ 和 react/store.js

前面咱们说到,reducer 的做用就是区别 action type 而后更新 state ,这里再也不赘述。可上手实际项目的时候,你会发现 action 类型和对应处理方式多起来会让单个 reducer 迅速庞大。

为此,咱们就得千方百计将其按业务逻辑拆分,以避免难以维护。可是如何把拆分后的 Reducer 组合起来呢 Redux 再次为咱们提供便捷 —— combineReducers 。

只有单一 Reducer 时,想必代码结构你也了然:

clipboard.png

咱们最终获得的 state 结构是:

  • state

    • demoAPP

当有多个 reducer 时:

clipboard.png

咱们最终获得的 state 结构是:

  • state

    • demoAPP
    • reducerB

想必你已经想到更进一步,把这些 Reducer 拆分到相应的文件模块下:

clipboard.png

接着,咱们来看 store 模块:

clipboard.png

怎么和想象的不同?不该该是这样吗:

clipboard.png

这里引入 redux 中间件的概念,你只需知道 redux 中间件的做用就是 在 action 发出之后,给咱们一个再加工 action 的机会 就能够了。

为何要引入 redux-thunk 这个中间件呢?

要知道,咱们此前所讨论的都是同步过程。实际项目中,只要遇到请求接口的场景(固然不仅有这种场景)就要去处理异步过程。

前面咱们知道,dispatch 一个 ActionCreator 会当即返回一个 action 对象,用以更新数据,而中间件赋予咱们再处理 action 的机会。

试想一下,若是咱们在这个过程当中,发现 ActionCreator 返回的并非一个 action 对象,而是一个函数,而后经过这个函数请求接口,响应就绪后,咱们再 dispatch 一个 ActionCreator ,此次咱们真的返回一个 action ,而后携带接口返回的数据去更新 state 。 这样一来不就解决了咱们的问题吗?

固然,这只是基本思路,关于 redux 的中间件设计,又是一个有趣的话题,有兴趣咱们能够再开一篇专门讨论,这里点到为止。

回到咱们的话题,通过

clipboard.png

这样包装一遍 store 后,咱们就能够愉快地使用异步 action 了:

clipboard.png

这里咱们用 promise 方式来处理请求,model.js 模块如你所想是一些接口请求 promise,就像这样:

clipboard.png

你也能够参阅咱们往期介绍的其余方式。

最后,咱们再来完善一下以前的流程:

clipboard.png

3.结语

Redux 的 API 一只手都能数得完,源码更是精炼,加起来不超过500行。但它给咱们带来的,不啻是一套复杂应用解决方案,更是 Flux 思想的精简表达。此外,你还能够从中体会到函数式编程的乐趣。

一千个观众心中有一千个哈姆莱特,你脑海里的又是哪个呢?

本文示例代码GitHub地址:https://github.com/ikcamp/rea...

参考

《Redux 官方文档》
《深刻 React 技术栈》

clipboard.png
图片描述

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

相关文章
相关标签/搜索