探索React的一切。
历史概要
React最早源于facebook内部的一个广告系统(2011年),2012年的时候被用于http://instagram.com,2013年5月向社区开源。
原理
单向数据流
“只要给定的数据是一定的,生成的东西就是一定的”。pure rendering component。
在React中,所有数据流都是单向的,不存在数据双向绑定。这带来的好处是,所有数据流向可控,不会出现莫名奇妙的双向绑定依赖问题。在开发大型web app时,你会发现,“可控性”是多么的重要。
Virtual DOM 和 diff 算法
原理层面,推荐阅读
计算一棵树形结构转换成另一棵树形结构的最少操作,传统diff算法需要循环递归比较节点,复杂度达O(n3)。React通过简化比较策略,将diff复杂度降低至O(n):
- tree diff
- component diff
- element diff
tree diff
核心:仅比较同一层级的节点,不考虑跨层级比较。
component diff
核心:仅继续比较同类型组件,不同类型组件直接判定为dirty。
当然,在继续比较同类型组件时,若组件shouldComponentUpdate()
返回true
,该组件也会被判定为dirty。
判定为dirty的组件会被重新渲染。
事实上,不同类型的组件渲染后的DOM结构有可能很相似,直接替换会造成性能下降。但这个相对激进的策略并不会造成什么困扰,因为“不同类型的 component 是很少存在相似 DOM tree 的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。”
element diff
核心:插入全新节点,移动可复用节点,删除老节点
需要注意的是,element diff遵循tree diff策略,即同层比较。
如何判断可复用的节点?同组同key的节点即可复用。
事件系统
Your event handlers will be passed instances of SyntheticEvent, a cross-browser wrapper around the browser’s native event. It has the same interface as the browser’s native event, including stopPropagation() and preventDefault(), except the events work identically across all browsers.
参考:
在DOM节点上绑定事件比较消耗内存,React采用的是顶层的事件代理机制,在根节点上绑定了一个监听器,并分发事件到组件中定义的事件监听器。
这种方式,也让React抹平了各个浏览器事件对象的差异(合成事件层)。可以通过合成事件的nativeEvent
熟悉,获得原生事件对象。
需要提出的是,react对每个渲染的节点,都赋予了唯一id属性。此id可用于合成事件分发和冒泡。
最佳实践
拥抱ES6
在React官网上,demo默认用了ES5的写法:
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="John" />, mountNode);
虽然没什么问题,但写法上限制较多,且无法享受React组件继承的好处。
JS在发展,拥抱ES6吧少年:
class HelloMessage extends React.Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
ReactDOM.render(<HelloMessage name="John" />, mountNode);
需要注意的是,目前大多数浏览器并未原生支持ES6语法,要借助babel将ES6转换成ES5。
阮老师写了个ES6语法探测网页,在线探测浏览器ES6友好度:http://ruanyf.github.io/es-checker/
最新版Chrome、Firefox、Opera都支持了90%的ES6特性,剩下10%,是因为浏览器端,没法实现真正的CMD加载特性require
和module.exports
。
CSS模块化
CSS 是前端领域中进化最慢的一块。由于 ES2015/2016 的快速普及和 Babel/Webpack 等工具的迅猛发展,CSS 被远远甩在了后面,逐渐成为大型项目工程化的痛点。也变成了前端走向彻底模块化前必须解决的难题。
由于CSS无命名空间特性,模块化是个粗暴的过程:
- 遵循BCM原则
- 选择器加命名空间前缀
- 借助工具,为选择器添加hash后缀
页面级的CSS模块化,参考阅读:
Flux模式
Flux 是 Facebook 使用的一套前端应用的架构模式
参考:
Flux模式目前有两个流行库实现: 1. React官方的Flux库 1. 社区的Redux库
这里就不详细对比两个库了。总的来说,Redux是比官方的Flux库更纯粹优雅的实现。
Redux 把自己标榜为一个“可预测的状态容器”,其实也是 Flux 里面“单向数据流”的思想,只是它充分利用函数式的特性,让整个实现更加优雅纯粹,使用起来也更简单。
Flux会在动作函数里分发动作和负载,但Redux把分发从动作函数里解耦了,Store变成了纯函数,输出只和入参有关,没有副作用。
Flux库的实现中,需要有一个分发器和至少一个的状态存储器;而Redux的最佳实践则要求仅使用一个状态存储器,不同组的状态通过集成的方式,挂载在状态存储器上。
性能优化
PC的性能在大部份情况下已经很好,在PC上一些存在的问题都被PC良好的性能掩盖下去。手机的性能不如PC,因此有更多有价值的东西深挖。
React号称,采用virtual dom技术方案,可以获得很高的性能。实际上,除非你采用了一系列的优化措施。
性能优化的课题总是很大,分析性能瓶颈是关键。考虑React的渲染逻辑,很明显在以下环节耗时多:
- 生成虚拟dom
- 虚拟dom和真实dom diff
- 更新真实dom
生成虚拟dom
核心:减少组件render()
方法调用次数。
shouldComponentUpdate()
方法中,判断是否需要重新渲染组件- 减少
setState()
方法调用次数
需要注意的是,shouldComponentUpdate()
方法会被频繁调用,应确保它的执行速度。
虚拟dom和真实dom diff
这里是否有优化点?是个需要考虑的问题。
更新真实dom
核心:减少虚拟dom和真实dom间的差异。
- 保持dom结构稳定
- 使用
display: none
等CSS属性代替增删节点 - 总是在节点组中设置节点key
React next
大胆猜测,React未来会怎么变化。
翻看React在github上的发布记录,从0.14到15.0大版本号跨越,主要改了这么几个地方:
- 开始使用
document.createElement()
- 不再需要在每个节点上添加
data-reactid
- 对
null
组件的处理 - SVG支持
- 大量顶层API被移除
从此不再支持IE8(鼓掌)。
回顾历史,往往可以粗略辨认未来的走向。React的未来——
不变的:
- 函数式编程模式
- 虚拟dom
- diff操作
改变的:
- diff算法
- 依赖的底层API
- 支持更多的特性
React有Facebook和社区两个靠山,未来很长时间都会是主流。
题外话
数据衡量页面性能
- 加载时间
- 渲染帧速
- 内存控制
HTTP next
当然是HTTP/2
HTTP2.0的核心(延时和吞吐量):
- 多路复用(解决网络延迟)
- 压缩头部
- 首部表
- 二进制分帧(改善延时和吞吐量)
- 请求优先级
- 服务器推送
HTML next
占坑
CSS next
在完成CSS 2.1的制定时,我们(CSSWG工作组)意识到了一个很大的问题就是版本号。它们很难去管理,进展也很缓慢。所以,我们决定把CSS这门语言分割成多个独立的模块,每个模块可以独立的分级,每个模块只包含了一小部分的功能,这样一来,某个进展缓慢的功能模块就不会拖慢整个规范的制定工作。
CSS3代指在CSS2.1之后发布的所有特性,并不是指版本号“3”o(╯□╰)o
目前草案阶段的新特性:
JS next
Webassembly
- WebAssembly
- WebAssembly:面向Web的通用二进制和文本格式
- 译 关于 WebAssembly 你应该知道的 7 件事
- asm.js:JavaScript的“汇编语言”
- Asm.js: Javascript的编译目标
WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.
webassembly是一套面向web的字节码格式。依赖工具链,可将C/C++源码转换成webassembly字节码。浏览器js引擎针对webassembly字节码做了专门的优化,可获得非常高的性能。
目前,webassembly有两套实现:
- Mozilla的asm.js
- Google的Native Client(NaCl)和Portable Native Client(PNaCl)
asm.js仅在nightly versions of Firefox中支持,性能仅仅比普通C++编译的慢两倍。
webassembly的目的是在计算密集型场景中,弥补JavaScript性能的缺陷。如游戏、加密解密、压缩、图形处理等。其并不是JavaScript的末日,而是相得益彰,让web端能做的事情更多。
至于webassembly给前端带来的变化,也许如同当年flash一般,将极大丰富用户的体验。比如突破JavaScript性能限制,把VR和AR带向前端。
尝鲜:https://webassembly.github.io/demo/
ES6、ES7
TypeScript
CoffeeScript