β

探索react-redux的小秘密

一、前言

自从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个方法:

具体的使用请参考官方文档以及starterkit
http://camsong.github.io/redux-in-chinese/docs/basics/UsageWithReact.html

https://github.com/davezuko/react-redux-starter-kit

参照Todo List,上手也很简单。总结起来就这么几个要点:

笔者也是依葫芦画瓢开始开发的,虽然流程程序跑起来没问题,但总有些细节想不明白,甚至因此都有些排斥Redux,觉得太复杂。相信刚接触的同学也会有类似的困惑:

读源码的需求来了…
分析完源码就知道这些问题的答案了。

三、容器组件与展示组件

在开始分析之前有两个名词需要了解一下。
80D262EA-40F3-44E5-B58A-3384B8CE2673.png-72.3kB
中文文档 里有这么一张图,当时看到我就懵逼了,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

将我们常用的组件分为两类,你会发现它们更容易被重用和理解,这两类我称之为容器组件和展示组件。

简单的总结了作者关于这两类组件的描述。

展示组件

应用中的大部分组件都属于此类

  1. 只关注于UI
  2. 会同时包含容器组件和展示组件,包含dom标签和样式
  3. 不依赖其他如flux的action、store之类的组件
  4. 只通过props来接受数据和回调函数
  5. 没有state或者只有一些改变UI的临时state,如toggleButton、sideBar等等

容器组件

router、react-native里的navigator等都属于此类

  1. 只关注于运作方式
  2. 同样会同时包含容器组件和展示组件,但一般没有自定义的样式,dom标签一般也都是作为wrapper来使用
  3. 调用flux的action
  4. 为展示组件提供数据和方法
  5. 作为数据源存储state,展示组件的UI变化控制器在这里

分类的好处

  1. UI与逻辑分离。更利于分析和维护
  2. 更容易复用。同样的UI,不同的数据源,操作起来不要太容易哦
  3. 展示组件可以提供给重构同学放肆的玩耍了

关于展示组件和容器组件的划分,不必过于教条,它们并没有严格的定义。可以一边写一边重构,必要的this.setState还是允许的。
更多详细的内容建议去看作者的原文。
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.i9dqo85xo

四、provider与connect分析

知晓展示组件和容器组件的区别,可以更容易帮助我们了解react-redux。
react-redux的源码看起来还是很轻松的,就这俩接口。

provider

A4B9{WOD@28~ADUFX~(85NY.png-29.6kB
核心代码就这么多,显然,它是一个容器组件。
关键点在:getChildContext,保存了全局唯一的store,类似于全局变量,子组件后续可以通过this.context.store来访问。

Context是React0.14新增的特性
https://facebook.github.io/react/docs/context.html
通过context传递属性的方式可以大量减少通过props 逐层传递属性的方式,可以减少组件之间的直接依赖关系。
这里是在为后面的connect组件打基础。

connect

connect方法是React与Redux连接的核心。

先来看看方法参数

Crayon Syntax Highlighter v_2.7.2_beta
1
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})
[Format Time: 0.0083 seconds]

从变量名就可以知道大概的意思。

mapStateToProps : 是一个函数,返回值是从Redux的state里挑出部分值,它们会合并到props里
mapDispatchToProps : 是一个函数,返回值是Redux的actionCreators,他们会合并到props里
mergeProps : 用于自定义需要合并props里的值
options :
pure=true 是否需要优化,等同于PureRenderMixin,不过这里的对比是由自定义的函数来完成
withRef="http://www.alloyteam.com/2016/03/10532/false 是否需要提供一个装饰器的引用

再来看看返回值

render方法:
QQ图片20160325104105.png-23.4kB

这段代码写的有的啰嗦~ wink

这也是一个容器组件。
定义了一个新的组件Connect,经过一系列的merge后,将各种值挂载props上传递到原组件。
Connect组件会保存state状态,同时监听Redux Store的变化,从而触发原组件的更新。

QQ截图20160325110520.png-56kB

核心步骤如下:

  1. 用Connect组件包装原有的组件
  2. 在componentDidMount中监听Provider提供的store的变化。通过Context来访问store。响应函数为handleChange。

  3. 在componentWillReceiveProps里判断新的props是否有改变,进而决定是否更新。
    这里这么做的原因是:
    Connect组件包含的展示组件里也可以有Connect组件,在上层的Connect组件发生改变时,亦会触发下层组件的重新render。

  4. handleChange里接收到通知后,将Connect组件的state改变为新的state,这里会有调用this.setState的操作。

  5. 触发了shouldComponentUpdate,这里做一次简单的优化,判断是否更新V-DOM

  6. 执行render方法。这里会再次对比connect方法传入的几个merge后props是否有更新。因为store是全局公用的,只是对比store.getState()显然不高效,也不合理,只有其中connect用到的值才需要update,这也是为什么我们要传入mapStateToProps的原因,如果不传,组件将不会触发更新。

  7. 更新包含的子组件

五、总结

react-redux就是定义了两个容器组件。

  1. 全局唯一Provider组件,利用Context特性提供Store给子组件使用
  2. Connect负责与React的展示组件进行交互,更新。
  3. 注入Connect时,切记只需要map组件需要的数据,减少不必要的性能消耗。
  4. Connect组件会以Connect作为displayName,方便调试。
  5. Redux的state与React的state之间并无任何联系,只在使用react-redux,在connect组件中才将其关联起来。
  6. 展示组件都属于无状态组件,自身不管理state(当然,一些临时状态值的存储还是需要的),state的管理都放在Connect组件里。
  7. Connect组件与展示组件可以互相嵌套。
    这个时候再看这张图,就感觉很清晰了。
    connect.png-40.8kB
  8. 如果babel支持了es7的注解(装饰器),那么写起来会更加的方便和直观
作者:Web前端 腾讯AlloyTeam Blog | 愿景: 成为地球卓越的Web团队!
腾讯全端 AlloyTeam 团队 Blog
原文地址:探索react-redux的小秘密, 感谢原作者分享。

发表评论