本文主要谈自己在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/blo...
另外,有的同学认为css modules并不够优雅,实际上上文的写法限定的问题就是一个麻烦事,所以我们可以用react-css-modules库,这个库解决了css modules的一些不是很好的问题,因为上手并不难,这里不详细介绍了(可以参考这里以及“深入react技术栈”73页)
2.组件间通信(非flux架构)
接下来我们总结一下react组件间通信的几种方式,虽然现在有了redux等最佳实践,但是很多时候我们还是需要原生可用的组件通信机制。
父组件向子组件之间
非常常见,通过props机制传递即可。
子组件向父组件通信
跨级组件通信
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 更改
我在这里没有给出代码,为了避免文章过于冗长以及和网上其他专题文章大部分重复,我主要是进行一些总结,具体内容我这里仍然是推荐一篇文章
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定义了不可变对象,一个数据结构(MapListArraySet)一旦被定义,就不可变了,我们把它如果用于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页)。
5.React 动画
缓动函数
对于各种动画来说,缓动体验一般是:linear < ease淡入淡出 < spring弹性动画\cubic bezier贝塞尔曲线
。
动画的方式有css动画和js动画,但是很多时候我们都是一起用的,所以区分的太详细似乎必要性也不大。
成熟的动画库
实际上动画经常是笔者比较忽视的一个方面,由于还没毕业,大多时候都是自己做小东西,最后动画就成了可有可无的环节,另外现在的各种动画库很多,方便到只需要一个class、只写一行代码就可以做出相对过得去的效果,自己也就疏于探索。
这部分内容主要推荐一些成熟的动画库。
首先是ReactCSSTransitionGroup,这个动画库提供了一些生命周期钩子,我们可以利用此加动画,具体学API的过程相当简单,我相信看懂上面各个部分的同学直接按照给出的链接肯定能顺利学会。
还有react-smooth动画库,这也是一个比较有意思的动画库,写法类似css的多关键帧动画。并且几种缓动函数动画这里都能实现。
react-motion也是一个值得推荐的动画库,如果想用spring动画这个似乎是更好的选择。
另外,不说react,还有一个让我印象深刻不得不提的就是vivus.js这个svg动画库,不得不说真是酷毙了。