react进阶漫谈

本文主要谈自己在react学习的过程中总结出来的一些经验和资源,内容逻辑参考了“深入react技术栈”一书以及网上的诸多资源,但也并非完全照抄,代码基本都是自己实践,主要为平时个人学习做一个总结和参考。

本文的关键内容:样式处理与css模块化、组件间通信(非flux架构)、组件抽象、组件性能优化以及React 动画五种内容。

1.样式处理与css模块化

在react出现之前,我们写样式一般是将css分离的,并且用less/sass预处理器,我个人在用backbone等MV*框架的时候就习惯用less并且用nodejs配置一个模块用来编译less。

但这样写会有一些问题:

  • 命名冲突是一个很常见的问题,因此,我们要制定出一套自己的完整命名规范来,并且要防止和项目中引入的库出现冲突。
  • 充分利用优先级是一个比较好的实践,但是这样写出的less代码有点像回调函数塔,虽然我本人并不觉得这有什么不好甚至还比较享受这种编程,但这的确不利于充分压缩css代码。

于是我们引入css modules。

简单的说,如果我们配置了css modules的话,那么你在css中写的类名和你在组件中写的class = ...都会被重新编译成一个哈希字符串,这样我们就不用考虑命名冲突的问题了,另外也可以比较自由的在local和global的css变量之间切换(实际上,这样的css变量默认都是local的,如果需要global,我们需要:global前缀,这样的话css变量就不会被转化成特殊的哈希值了)

需要注意的是写法问题,这个时候我们就不能在jsx中仅仅用className了,css module实际上限制了我们必须要用className={style.title}这样的写法,实际上我在尝试的时候因为这个地方的bug调试了很久,而这也在某一种程度上给利用css module进行重构代码带来了一些困难。

关于css modules的入门介绍,没错,阮一峰老师写了一份:http://www.ruanyifeng.com/blog/2016/06/css_modules.html

另外,有的同学认为css modules并不够优雅,实际上上文的写法限定的问题就是一个麻烦事,所以我们可以用react-css-modules库,这个库解决了css modules的一些不是很好的问题,因为上手并不难,这里不详细介绍了(可以参考这里以及“深入react技术栈”73页)

2.组件间通信(非flux架构)

接下来我们总结一下react组件间通信的几种方式,虽然现在有了redux等最佳实践,但是很多时候我们还是需要原生可用的组件通信机制。

父组件向子组件之间

非常常见,通过props机制传递即可。

子组件向父组件通信
  • 利用回调函数,回调函数本身定义在父组件中,通过props方式传递给子组件,在子组件中调用回调函数。
  • 利用自定义事件机制,这种方法更通用方便,并且可以简化API,关于自定义事件机制的详细使用方法我们在接下来展开。
跨级组件通信
  • context机制。不过这种机制react并不是特别推荐(不是特别推荐并不代表会在将来的版本没有,只是说明可能会产生一定的弊端因此要慎用少用),context机制需要在上级组件(可以是父组件的父组件)定义一个getChildContext函数如下:
getChildContext(){
    return{
        color:"red",
    }
}
  • 当然也可以用事件机制
没有层级关系的组件通信

这回只能用事件机制了,虽然我之前分析过别的框架的事件机制部分都可以单独拎出来用,但是这里面实际上有好多方式。

我首先试了一下js-signals这个库,这个也是React团队使用的,用起来也还简单,npm install signals之后,我们可以单独写一个Signal文件:

const signals= require('signals');

var Signal = {
  started : new signals.Signal()
};

我们可以把接收事件的函数定义在组件B中:

 onStarted(param1, param2){
        alert(param1 + param2);
 }
 constructor(props){
        super(props);
        Signal.started.add(this.onStarted); //add listener
 }

然后在组件A中(注意dispatch的时候要保证B已经被构造出来了):

 handlethis () {
     Signal.started.dispatch('foo', 'bar'); //dispatch signal passing custom parameters
 }

 render(){
     return (
         <button onClick={this.handlethis}>发射事件</button>
     )
 }

其实还有很多类似的组件,当然我们自己写一个功能弱的也不成问题,更多的方式,这篇文章介绍的不错。

3.组件抽象

mixin

mixin是一个饱受诟病的东西,另外蛋疼的是在ES6的写法下也不能用,笔者现在写react的时候都已经不用了,所以这里进行简单介绍。

我们可以通过在createClass的时候传入一个mixins数组,这个数组里是我们的一些通用的方法:

React.createClass({
    mixins:[method1,method2]
    //...
})

这在ES6的class形式下是不能“直接”使用的。

ES7 decorator 与 mixin

ES7 的 decorator,作用就是返回一个新的 descriptor,并把这个新返回的 descriptor 应用到目标方法上。稍后我们将会看到,decorator 并非只能作用到类的方法/属性上,它还可以作用到类本身。

当然,这个我只言片语肯定说不明白的,这个我要推荐淘宝前端团队的这篇文章

另外,core-decorators这个库值得关注,它里面有一个mixin方法用于实现mixin,原理就是用了ES7 decorator,实现起来也不是非常复杂。

最后,提醒一下ES7 decorator虽然很酷,但是目前还处于提案阶段,虽然借助babel我们已经可以体验了,但是距离真正支持还有一段距离

高阶组件(HOC)

这是一个颇值得一提的话题。

我们应该听说过高阶函数,这种函数接受函数作为输入,或者是输出一个函数,比如map、reduce以及sort等函数。

一个高阶组件只是一个包装了另外一个 React 组件的 React 组件, 这种包装通常有两种方式:

1、属性代理(Props Proxy):高阶组件操控传递给 WrappedComponent 的 props,
2、反向继承(Inheritance Inversion):高阶组件继承(extends)WrappedComponent。

高阶组件的功能主要有以下几点:

1、代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
2、渲染劫持

  • 渲染劫持主要通过反向继承来实现,我们可以选择是否渲染原组件,也可以改变原组件的渲染结果(注意:我们通过 var elementsTree = super.render()可以拿到原组件的渲染结果,然后我们可以改变props之后,通过原生cloneElement方法创建出新的节点树)

3、State 抽象和更改

  • 所谓抽象state的目的,就是将原组件作为一个纯粹的展示型组件,分离内部状态,将state交给高阶组件来控制。比如:我们可以抽象出一个控制input的高阶组件,从而不用在input中来有很多控制state的代码。

4、Props 更改

  • 我们可以读取、增加、编辑、删除被包裹组件的props

我在这里没有给出代码,为了避免文章过于冗长以及和网上其他专题文章大部分重复,我主要是进行一些总结,具体内容我这里仍然是推荐一篇文章

4.组件性能优化

PureRender

PureRender这个概念实际上和纯函数有关,Pure指的是对同样的输入(对于react来说就是props和state)总是得到相同的输出,针对这个问题,React有一个shouldComponentUpdate钩子,这个钩子默认返回true,用于props或者state改变或者接收到新的值时候,可以供用户重写,这样在接受到相同的props的时候我们就可以防止其重新渲染。

PureRenderMixin在这个时候要派上用场了,这是一个能够实现上述功能的官方插件,react是这样介绍它的:

If your React component's render function renders the same result given the same props and state, you can use this mixin for a performance boost in some cases.

实际上是通过一个浅比较来确定是不是该被渲染,这实际上是一个性能上的权衡和妥协,深比较真的是耗费太多(我们在下一节会提出一个更好的解决方案)。
写法也比较简单:

import PureRenderMixin from 'react-addons-pure-render-mixin';
class FooComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

我们可以在这里查看更多信息。

Immutable.js

在传递数据的时候,我们可以用Immutable Data进一步提高性能。

Immutable.js定义了不可变对象,一个数据结构(Map\List\ArraySet)一旦被定义,就不可变了,我们把它如果用于state,那么每次变化的时候需要将state整个重新赋值。

上文提到,在shouldComponentUpdate使用PureRenderMixin由于性能权衡我们只能使用浅比较,但是如果我们用了Immutable.js,我们有更好的方式:直接用=== or is就可以判断,因为Immutable.js比较的是两个对象的hashCode或者valueOf,并且内部使用了trie数据结构(比如字典树)来存储,因此性能很高。

另外,由于Immutable.js中提供的数据结构是不可变的,我们不用担心js中源对象跟随引用对象的变化而变化的问题,也不用考虑函数中所谓的引用赋值,这给我们的编程带来了很多方便。

当然也有不方便的是Immutable.js的数据结构并不能和原生的数据结构混用,因此写法上需要格外注意,关于更多资料请看这里.

无状态组件

生命周期让react的组件变得功能非常强大并且复杂,从而难以维护,而有的时候我们又要经常写一些自身没有状态,只从父组件接受props的组件,这种组件可以提高react的渲染性能,也被官方推荐。

const HelloWorld = (props) => <div>{props.name}</div>
ReactDOM.render(<HelloWorld name="HelloWorld" />,App)

简单,高效,在有些不需要改变的地方,比如没有用户交互纯声明性质的内容,可以用无状态组件。

react的diff算法

我们想要让效率更高,还要注意的一点就是要照顾react的diff算法,react虽然有一个复杂度仅为O(N)的diff算法,但是这个算法也不是万能的,我们要想让react效能最大化,就要去照顾这个diff算法。

总的说来,这个diff算法大概有三点实现概要:

  • 对两棵树进行比较,react认为,对节点的跨层级操作移动较少,所以只会对相同层级的dom节点进行比较,即同一个父节点下的字节点,当发现节点已经不存在时,就会删除节点,当发现节点新增时候,就会插入节点。
    • 为了迎合这个策略,我们尽量不要对dom节点进行跨层级操作(比如把某一个字节点转而挂在到某一个孙节点下面),因为这样效率是比较低的。
  • 对组件之间进行比较:如果是同一个类型的组件,按照第一条策略进一步比较虚拟dom树;如果不是,就将该组件判断为dirty,从而替换所有字节点;对于同一类型的组件,有可能其虚拟dom树没有发生变化,如果能够确切知道这一点,那么就可以节省大量diff的操作时间,因此,react允许用户通过shouldComponentUpdate钩子来判断组件是否发生变化。
    • 为了迎合这个策略,我们可以使用上面提到的PureRender或者Immutable.js。
  • 当节点处于同一个层级,react提供了插入、移动、删除操作,这里主要指相似节点,比如<li>标签,因此react允许开发者将同一个层级的节点添加唯一key进行操作,同一个key认为是相同节点。之后react有一套自己的算法规则,对节点进行移动操作以达到要求(具体可以参考“深入react技术栈”176页)。
    • 为了迎合这一规则,我们要给li标签等添加一个key(实际上已经被react强制),另外,在开发过程中尽量减少将最后一个节点移动到第一个的情况,因为这个时候react要进行很多的移动操作。

5.React 动画

缓动函数

对于各种动画来说,缓动体验一般是:linear < ease淡入淡出 < spring弹性动画\cubic bezier贝塞尔曲线

动画的方式有css动画和js动画,但是很多时候我们都是一起用的,所以区分的太详细似乎必要性也不大。

成熟的动画库

实际上动画经常是笔者比较忽视的一个方面,由于还没毕业,大多时候都是自己做小东西,最后动画就成了可有可无的环节,另外现在的各种动画库很多,方便到只需要一个class、只写一行代码就可以做出相对过得去的效果,自己也就疏于探索。

这部分内容主要推荐一些成熟的动画库。

首先是ReactCSSTransitionGroup,这个动画库提供了一些生命周期钩子,我们可以利用此加动画,具体学API的过程相当简单,我相信看懂上面各个部分的同学直接按照给出的链接肯定能顺利学会。

还有react-smooth动画库,这也是一个比较有意思的动画库,写法类似css的多关键帧动画。并且几种缓动函数动画这里都能实现。

react-motion也是一个值得推荐的动画库,如果想用spring动画这个似乎是更好的选择。

另外,不说react,还有一个让我印象深刻不得不提的就是vivus.js这个svg动画库,不得不说真是酷毙了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 深入JSX date:20170412笔记原文其实JSX是React.createElement(componen...
    gaoer1938阅读 7,981评论 2 35
  • 自己最近的项目是基于react的,于是读了一遍react的文档,做了一些记录(除了REFERENCE部分还没开始读...
    潘逸飞阅读 3,190评论 1 10
  • react 基本概念解析 react 的组件声明周期 react 高阶组件,context, redux 等高级...
    南航阅读 1,009评论 0 1
  • 谢女且听词,有心话你知: 眼盈盈、凝睇清眉 好似开娇横远岫,又中意、鬓边丝 一遇目双垂,相思付与谁 得闲时、欲语还...
    赵莲贵阅读 368评论 3 6
  • 开这个号的目的就是为了写写写。 不求阅读量,不求打赏,就是想写东西的时候写点什么。 不用有所顾及,表达最真实的想法...
    麦田328阅读 102评论 0 0