一、前言
自从 React 火起来后,笔者对这种组件化的开发模式实在太喜欢,瞬间成为了它的脑残粉。后面也用 React 做了一些项目,比如 http://buluo.qq.com/p, 采用的技术架构是 Reactjs + Reflux + webpack。不得不说前端的变化是日新月异,Redux 出来后,github star 嗖嗖的,用 Reflux 就显得很 low B 了,迎头赶上吧。
这是 Redux 的架构图。
Redux 起源于 React,但它们并没有关系。它是独立的,支持 React、Angular、Ember 或者更多其他的框架。网上有非常多的文章去讨论 Redux,本文就不去讨论它的原理及使用方法了,不了解的同学请移步 Redux 中文文档。
既然 Redux 与 React 没有什么关联,那要怎样搭配它们使用呢?
官方提供了一个 react-redux 绑定库,来配合 React,它是怎么实现的呢?
OK,本文就是来讲 react-redux 的。
二、不介绍 react-redux 的使用
就提供了 2 个方法:
- Provider
- connect
具体的使用请参考官方文档以及 starterkit
http://camsong.github.io/redux-in-chinese/docs/basics/UsageWithReact.html
https://github.com/davezuko/react-redux-starter-kit
参照 Todo List,上手也很简单。总结起来就这么几个要点:
- Redux 提供唯一 store。
- 用 Provider 组件包含住最顶层的组件,将 store 作为 props 传入。
- 用 connect 方法将 store 树结构中数据以及 actions 通过 props 传递到业务子组件
- 子组件调用 action,dispath 到 reducer 返回新的 state,同步到 store 的树结构中,通知组件进行更新
笔者也是依葫芦画瓢开始开发的,虽然流程程序跑起来没问题,但总有些细节想不明白,甚至因此都有些排斥 Redux,觉得太复杂。相信刚接触的同学也会有类似的困惑:
- provider 和 connect 是干什么的?为什么要包住最顶层?
- 例子里没有看到任何地方有调用 setState,React 组件是怎么做到自动更新的呢?
读源码的需求来了...
分析完源码就知道这些问题的答案了。
三、容器组件与展示组件
在开始分析之前有两个名词需要了解一下。
中文文档里有这么一张图,当时看到我就懵逼了,React 里啥时候有这么两个组件了?
其实,这并不是新的组件,这是 Redux 作者总结出来的一种模式。
You’ll find your components much easier to reuse and reason about if you divide them into two categories. I call them Container and Presentational components
将我们常用的组件分为两类,你会发现它们更容易被重用和理解,这两类我称之为容器组件和展示组件。
简单的总结了作者关于这两类组件的描述。
展示组件
应用中的大部分组件都属于此类
- 只关注于 UI
- 会同时包含容器组件和展示组件,包含 dom 标签和样式
- 不依赖其他如 flux 的 action、store 之类的组件
- 只通过 props 来接受数据和回调函数
- 没有 state 或者只有一些改变 UI 的临时 state,如 toggleButton、sideBar 等等
容器组件
router、react-native 里的 navigator 等都属于此类
- 只关注于运作方式
- 同样会同时包含容器组件和展示组件,但一般没有自定义的样式,dom 标签一般也都是作为 wrapper 来使用
- 调用 flux 的 action
- 为展示组件提供数据和方法
- 作为数据源存储 state,展示组件的 UI 变化控制器在这里
分类的好处
- UI 与逻辑分离。更利于分析和维护
- 更容易复用。同样的 UI,不同的数据源,操作起来不要太容易哦
- 展示组件可以提供给重构同学放肆的玩耍了
关于展示组件和容器组件的划分,不必过于教条,它们并没有严格的定义。可以一边写一边重构,必要的 this.setState 还是允许的。
更多详细的内容建议去看作者的原文。
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.i9dqo85xo
四、provider 与 connect 分析
知晓展示组件和容器组件的区别,可以更容易帮助我们了解 react-redux。
react-redux 的源码看起来还是很轻松的,就这俩接口。
provider
核心代码就这么多,显然,它是一个容器组件。
关键点在:getChildContext, 保存了全局唯一的 store, 类似于全局变量,子组件后续可以通过 this.context.store 来访问。
Context 是 React0.14 新增的特性
https://facebook.github.io/react/docs/context.html
通过 context 传递属性的方式可以大量减少通过 props 逐层传递属性的方式, 可以减少组件之间的直接依赖关系。
这里是在为后面的 connect 组件打基础。
connect
connect 方法是 React 与 Redux 连接的核心。
先来看看方法参数
1 |
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) |
从变量名就可以知道大概的意思。
mapStateToProps: 是一个函数,返回值是从 Redux 的 state 里挑出部分值,它们会合并到 props 里
mapDispatchToProps: 是一个函数,返回值是 Redux 的 actionCreators,他们会合并到 props 里
mergeProps: 用于自定义需要合并 props 里的值
options:
pure=true 是否需要优化, 等同于 PureRenderMixin,不过这里的对比是由自定义的函数来完成
withRef=false 是否需要提供一个装饰器的引用
再来看看返回值
render 方法:
这段代码写的有的啰嗦~
这也是一个容器组件。
定义了一个新的组件 Connect,经过一系列的 merge 后,将各种值挂载 props 上传递到原组件。
Connect 组件会保存 state 状态,同时监听 Redux Store 的变化,从而触发原组件的更新。
核心步骤如下:
- 用 Connect 组件包装原有的组件
-
在 componentDidMount 中监听 Provider 提供的 store 的变化。通过 Context 来访问 store。响应函数为 handleChange。
-
在 componentWillReceiveProps 里判断新的 props 是否有改变,进而决定是否更新。
这里这么做的原因是:
Connect 组件包含的展示组件里也可以有 Connect 组件,在上层的 Connect 组件发生改变时,亦会触发下层组件的重新 render。 -
handleChange 里接收到通知后,将 Connect 组件的 state 改变为新的 state,这里会有调用 this.setState 的操作。
-
触发了 shouldComponentUpdate, 这里做一次简单的优化,判断是否更新 V-DOM
-
执行 render 方法。这里会再次对比 connect 方法传入的几个 merge 后 props 是否有更新。因为 store 是全局公用的,只是对比 store.getState() 显然不高效,也不合理,只有其中 connect 用到的值才需要 update,这也是为什么我们要传入 mapStateToProps 的原因,如果不传,组件将不会触发更新。
-
更新包含的子组件
五、总结
react-redux 就是定义了两个容器组件。
- 全局唯一 Provider 组件,利用 Context 特性提供 Store 给子组件使用
- Connect 负责与 React 的展示组件进行交互,更新。
- 注入 Connect 时,切记只需要 map 组件需要的数据,减少不必要的性能消耗。
- Connect 组件会以 Connect 作为 displayName,方便调试。
- Redux 的 state 与 React 的 state 之间并无任何联系,只在使用 react-redux, 在 connect 组件中才将其关联起来。
- 展示组件都属于无状态组件,自身不管理 state(当然,一些临时状态值的存储还是需要的),state 的管理都放在 Connect 组件里。
-
Connect 组件与展示组件可以互相嵌套。
这个时候再看这张图,就感觉很清晰了。
-
如果 babel 支持了 es7 的注解 (装饰器),那么写起来会更加的方便和直观
匿名 2017 年 1 月 30 日
赞赞赞👍
前端小渣 2016 年 10 月 19 日
文章写得真好,看过的中文文章里解释 react-redux 最清晰的一篇
小易分享网 2016 年 8 月 16 日
文章很好~!赞 http://www.xevip.cn
undefined 2016 年 4 月 8 日
总结很赞
vvv阿城 2016 年 4 月 1 日
看了一下 http://buluo.qq.com/p/category.html 页面,点击左边 category 导航的时候向后端请求这个分类里面包含的数据渲染到右边内容,但是每次点一个之后都会重新请求数据,比如现在点了小说,ajax 请求了小说分类的数据,我再点文艺,同样 ajax 请求了文艺的数据,我再点回小说又重新请求了一次小说数据,数据和之前那个是重复的貌似,既然用了 redux,为什么不把数据放到 store,下次渲染就不需要多一次请求呢,这点我有点不明白 😂
问题石 2016 年 4 月 6 日
1. http://buluo.qq.com/p/category.html 这个项目做的比较早,也不是 SPA,用的是 reflux,没有用 redux
2. 分类的切换需要重新请求的原因是: 热门排行的数据是实时变化的。当然如果数据没有变化,react 会帮助我们 diff,只是多拉取了一次数据,而不会重新渲染。
罗大旭 2016 年 3 月 28 日
redux 中间件写起来才是爽