声明:本文来自腾讯增值产品部官方公众号小时光茶社,为CSDN原创投稿,未经许可,禁止任何形式的转载。
作者:左明,企鹅电竞前端团队leader,腾讯高级工程师。从事web开发超过8年,主导过微云web版、腾讯云助手、手Q游戏公会、企鹅电竞App等项目,有丰富的前端架构经验。
责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件[email protected],另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。
首先,我们来看看React在世界范围的热度趋势,下图是关键词“房价”和“React”在Google Trends上的搜索量对比,蓝色的是React,红色的是房价,很明显,人类对React的关注程度已经远远超过了对房价的关注。
从这些数据中,大家能看出什么?
可以很明显的看出,我在一本正经的扯淡。
从2014年到现在,React、jQuery和Angular的热度趋势对比,可以很明显的看到(上图),React在全球的热度趋势增长非常快。
上图是React在国内的百度搜索指数,是拿React和Node.js做了个对比,可以看出React的关注度也已经超过了Node.js。
虽然在关注总量上React还远不及jQuery和Angular等,但其增长速度超乎想象,你知道这意味着什么吗?
这意味着关注React,你就已经走在了业界的前沿!
那么React到底是什么鬼?
官网说“一个用来构建用户界面的JavaScript库”。
React起源于Facebook的内部项目,由于React的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来Web开发的主流工具。
和Backbone、Angular等MV*框架不一样,它只处理View逻辑。所以如果你喜欢它,你可以很容易的将它接入到现有工程中,然后用React重写HTML部分即可,不用修改逻辑。
React这么火,那么它到底有什么牛逼的地方?
上图是2015年年初的数据。
这是Facebook的好友动态页面,也是Facebook访问量最大的页面没有之一,通过Chrome React插件可以看到这个页面确实是用React实现的。
这个图截的比较早,现在应该更多了。
前面给大家来了一波前戏,相信大家已经有点迫不及待了,那么,进入正题:
首先,先跟大家描述下React最特别的部分,听完这部分大家基本就能够在脑海里建立起一个React的大致印象。
然后是React的核心内容,听完这部分大家待会回去就可以开始写代码了。
最后是React能够给我们实际带来的价值,我们不做无意义的重构。
首先,我们来看JSX——
我们先说说模板。
HTML模板有很多种编写方式,这是一种:
还有这种:
还有多年前流行的Script标签模板。
这种运行时编译的模板调试起来比较困难,尤其是出现错误时,很难定位,如果要定位,需要做很多额外的工作,麻烦。
近两年构建工具的流行,很多团队已经在使用Grunt等构建工具来预编译模板,从而简化模板开发,提高运行效率,减少维护成本。
JSX使用的也是预编译模板,和前面看到的没什么差别,细心的同学会发现好像只是没有字符串引号了而已。
React提供了一个预编译工具,叫react-tools,可以通过NPM命令安装,一般是在开发时期运行,运行后它会启动一个监听程序,实时监听JSX源码的修改,然后自动编译为JS代码。
大家留意下,里面的UL LI被编译成了React.createElement(),它这么做,目的就是为了实现虚拟DOM。
JSX还支持运行时编译,但是为了推荐大家用预编译,所以我不打算告诉你们运行时编译怎么做。
祝贺大家已经精通了JSX。
接下来我们来了解React最大的亮点——虚拟DOM。
传统Web App和DOM直接交互,由App来控制DOM的构建和渲染、元素属性的读写、事件的注册和销毁等。
当新产品刚上线时,这种做法看起来也挺好。但随着产品功能越来越丰富、代码量越来越多、开发团队人员越来越多——
一年后,你的代码可能会变成这样:
当然,合理的代码规划能够避免这类问题,但团队里难免会有擅长屠宰式编程的同学,分分钟把你代码改得面目全非。
这时,React的虚拟DOM和单项数据流就能很好的解决这个问题。
虚拟DOM则是在DOM的基础上建立了一个抽象层,我们对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中。
虚拟DOM会使得App只关心数据和组件的执行结果,中间产生的DOM操作不需要App干预,而且通过虚拟DOM来生成DOM,会有一项非常可观的收益——性能。
所有人都知道DOM操作慢,渲染一个空的DIV,浏览器需要为这个DIV生成几百个属性,而虚拟DOM只有6个。
所以说减少不必要的重排重绘以及DOM读写能够对页面渲染性能有大幅提升。
那么我们来看看虚拟DOM是怎么做的。React会在内存中维护一个虚拟DOM树,当我们对这个树进行读或写的时候,实际上是对虚拟DOM进行的。当数据变化时,然后React会自动更新虚拟DOM,然后拿新的虚拟DOM和旧的虚拟DOM进行对比,找到有变更的部分,得出一个Patch,然后将这个Patch放到一个队列里,最终批量更新这些Patch到DOM中。
这样的机制可以保证即便是根节点数据的变化,最终表现在DOM上的修改也只是受这个数据影响的部分,这样可以保证非常高效的渲染。
但也是有一定的缺陷的——首次渲染大量DOM时因为多了一层虚拟DOM的计算,会比innerHTML插入方式慢。
一个最基本的React组件由数据和JSX两个主要部分构成,我们先来看看数据。
这是一个简单单完整的React组件【类】,细节大家先不用太在意细节,了解机制就可以。
props主要作用是提供数据来源,可以简单地理解为props就是构造函数的参数。
state唯一的作用是控制组件的表现,用来存放会随着交互变化的状态,比如开关状态等。
JSX做的事情就是根据state和props中的值,结合一些视图层面的逻辑,输出对应的DOM结构。
组件复合:
前面我们知道了一个简单组件的构成,但单个组件肯定不能满足实际需求,我们需要做的是将这些独立的组件进行组装,同时找出共性的部分进行复用。
我们拿这么一个网站来进行示例,大的模块有中心主体区域,左侧有K吧列表、应用列表等,可以看出,里面的
都是最细粒度的组件,可以复用。首先,我们来看下Article的代码——
这就是我们分解出来的Article组件,它需要2个属性,Article对象和ShowImage。Article对象包含图片、地址、标题、描述信息,ShowImage是一个布尔类型,用来判断是否需要显示成一个图片。
这个组件本身的实现可以很简单也可以很复杂,但使用者可不关心你的内部实现,使用者关心组件需要什么参数就可以了。
外国人的组件化思想比我们国内的普及程度高很多,不只局限于软件开发,包括实体行业的咖啡机、加油站、儿童摇摇车都有这种设计思想在里面。
将复杂的UI逻辑保留在组件内部。
希望大家在设计模块的时候,也尽可能将组件逻辑对外透明,来减少维护成本。
我们继续看热点区域,大家留意一下标虚线的部分,这里复用了Article组件。这时的Article组件看起来就是一个普通的标签而已,简单吧。
这个是热问组件,也复用了Article组件。
这就是React如丝般顺滑的组件复合。
这个,叫做竹笕,是中日传统禅文化中常见的庭院装饰品,它的构造可简单可复杂,但原理很简单,比如这个竹笕,水从竹笕顶部入口流入内部,并按照固定的顺序从上向下依次流入各个小竹筒,然后驱动水轮转动。对于强迫症患者来说,观赏竹笕绝对是一个很享受的过程,你会发现这些小玩意竟然能这么流畅的协调起来,好神奇。
如果竹笕是一个组件的话,那么水就是组件的数据流。
在React中,数据流是自上而下单向地从父节点传递到子节点,所以组件是简单且容易把握的,他们只需要从父节点提供的props中获取数据并渲染即可。如果顶层组件的某个prop改变了,React会递归地向下遍历整棵组件数,重新渲染所有使用这个属性的组件。
这个是前面看到的KM热点问题组件,拥有一个叫做Articles的属性。
在组件内部,可以通过this.props来访问props,props是组件唯一的数据来源,对于组件来说:
props永远是只读的。
不要尝试在组件内部调用setProps的方法来修改props,如果你不小心这么做了,React会报错并给出非常详细的错误提示。
组件的属性类型如果不进行声明和验证,那么很可能使用者传给你的属性值或者类型是无效的,那会导致一些意料之外的故障。好在React已经为我们提供了一套非常简单好用的属性校验机制——
React有一个PropTypes属性校验工具,经过简单的配置即可。当使用者传入的参数不满足校验规则时,React会给出非常详细的警告,定位问题不要太容易哦。
PropTypes包含的校验类型包括基本类型、数组、对象、实例、枚举——
以及对象类型的深入验证等。如果内置的验证类型不满足需求,还可以通过自定义规则来验证。
如果某个属性是必须的,在类型后面加上isRequired就可以了。
state:
React的一大优点,就是把每一个组件都看成是一个状态机,组件内部通过state来维护组件状态的变化,这也是state唯一的作用。
state一般和事件一起使用,我们先看state,然后看看state和事件怎样结合。
这是一个简单的开关组件,开关状态会以文字的形式表现在按钮的文本上。
首先看render方法,返回了一个button元素,给button注册了一个事件用来处理点击事件,在点击事件中对state的on字段取反,并执行this.setState()方法设置on字段的新值。一个开关组件就完成了。
组件渲染完成后,必须有UI事件的支持才能正常工作。
React通过将事件处理器绑定到组件上来处理事件。
React事件本质上和原生JS一样,鼠标事件用来处理点击操作,表单事件用于表单元素变化等,React事件的命名、行为和原生JS差不多,不一样的地方是React事件名区分大小写。
比如这段代码中,Article组件的section节点注册了一个onClick事件,点击后弹出alert。
有时候,事件的处理器需要由组件的使用者来提供,这时可以通过props将事件处理器传进来。
这个是刚才那个Article组件的使用者,它提供给Article组件的props中包含了一个onClick属性,这个onClick指向这个组件自身的一个事件处理器,这样就实现了在组件外部处理事件回调。
这是一个React组件实现组件可交互所需的流程,render()输出虚拟DOM,虚拟DOM转为DOM,再在DOM上注册事件,事件触发setState()修改数据,在每次调用setState方法时,React会自动执行render方法来更新虚拟DOM,如果组件已经被渲染,那么还会更新到DOM中去。
这些是React目前支持的事件列表。
React的组件拥有一套清晰完整而且非常容易理解的生命周期机制,大体可以分为三个过程:初始化、更新和销毁,在组件生命周期中,随着组件的props或者state发生改变,它的虚拟DOM和DOM表现也将有相应的变化。
首先是初始化过程,这里不太好理解,字比较多。
组件类在声明时,会先调用getDefaultProps()方法来获取默认props值,这个方法会且只会在声明组件类时调用一次,这一点需要注意,它返回的默认props由所有实例共享。
在组件被实例化之前,会先调用一次实例方法getInitialState()方法,用于获取这个组件的初始state。
实例化之后就是渲染,componentWillMount方法会在生成虚拟DOM之前被调用,你可以在这里对组件的渲染做一些准备工作,比如计算目标容器尺寸然后修改组件自身的尺寸以适应目标容器等。
接下来就是渲染工作,在这里你会创建一个虚拟DOM用来表示组件的结构。对于一个组件来说,render是唯一一个必须的方法。render方法需要满足这几点:
需要注意的是,render方法返回的是虚拟DOM。
渲染完成以后,我们可能需要对DOM做一些操作,比如截屏、上报日志,或者初始化iScroll等第三方非React插件,可以在componentDidMount()方法中做这些事情。当然,你也可以在这个方法里通过this.getDOMNode()方法最终生成DOM节点,然后对DOM节点做爱做的事情,但需要注意做好安全措施,不要缓存已经生成的DOM节点,因为这些DOM节点随时可能被替换掉,所以应该在每次用的时候去读取。
组件被初始化完成后,它的状态会随着用户的操作、时间的推移、数据更新而产生变化,变化的过程是组件声明周期的另一部分——
上图为更新过程。
当组件已经被初始化后,组件调用者修改组件的属性时,组件的componentWillReceiveProps()方法会被调用,在这里,你可以对外部传入的数据进行一些预处理,比如从props中读取数据写入state。
默认情况下,组件调用者修改组件属性时,React会遍历这个组件的所有子组件,进行“灌水”,将props从上到下一层一层传下去,并逐个执行更新操作,虽然React内部已经进行过很多的优化,这个过程是很快的,如果你追求极致性能或者你发现这个过程花费了太久时间,使用shouldComponentUpdate()——
有时候,props发生了变化,但组件和子组件并不会因为这个props的变化而发生变化,打个比方,你有一个表单组件,你想要修改表单的name,同时你能够确信这个name不会对组件的渲染产生任何影响,那么你可以直接在这个方法里return false来终止后续行为。这样就能够避免无效的虚拟DOM对比了,对性能会有明显提升。
如果这个时候有同学仍然饥渴难耐,那么你可以尝试 不可变数据结构(用过mongodb的同学应该懂)。
组件在更新前,React会执行componentWillUpdate()方法,这个方法类似于前面看到的componentWillMount()方法,唯一不同的地方只是这个方法在执行的时候组件是已经渲染过的。需要注意的是,不可以在这个方法中修改props或state,如果要修改,应当在componentWillReceiveProps()中修改。
然后是渲染,React会拿这次返回的虚拟DOM和缓存中的虚拟DOM进行对比,找出【最小修改点】,然后替换。
更新完成后,React会调用组件的componentDidUpdate方法,这个方法类似于前面的componentDidMount方法,你仍然可以在这里可以通过this.getDOMNode()方法取得最终的DOM节点。
香港电影结尾经常看到一个剧情,就是英雄打败了坏人,然后警察出来擦屁股——
销毁:
警察偶尔还能立功,而componentWillUnmount最可怜,他除了擦屁股什么也做不了。
你可以在这个方法中销毁非React组件注册的事件、插入的节点,或者一些定时器之类。这个过程要小心处理,如果处理不当可能导致内存泄漏,比如实例化了某个第三方插件却没销毁,这个React可帮不了你,你自己约的炮,含着泪也要打完。
看demo点我(记得打开控制台)
下面我们来看看React怎样结合Node.js实现服务端渲染。
服务端渲染有多快我就不多说了。
因为有虚拟DOM的存在,React可以很容易地将虚拟DOM转换为字符串,这便使我们可以只写一份UI代码,同时运行在node里和浏览器里。
在node里将组件渲染为一段HTML只需要一句代码。
不过围绕这个renderToString我们还要做一些准备工作。代码有点多:
这是一个express的路由方法,这段代码做了这几件事:
需要注意的是这里的JSON字符串中可能出现结尾标签或HTML注释,可能会导致语法错误,这里需要进行转义。
页面的示例代码本来打算用大家更熟悉的HTML,但是为了便于阅读,所以换成了jade代码。
这个页面做了这几件事:
这就是React的服务端渲染,组件的代码前后端都可以复用。
当然React适用的场景不止这些
(实际数据以GitHub为准)
React-Native能够用一套代码同时运行在浏览器和node里,而且能够以原生App的姿势运行在iOS和Android系统中,既拥有Web迭代迅速的特性,又拥有原生App的体验。
这是React和React-Native在GitHub上的数据,可以看出React-Native也是相当热门——因为React-Native能够使React的价值最大化——对老板来说,意味着有前端工程师也可以终端开发,节省了人力成本,同时对前端开发工程师来说又多了一个走向全栈的捷径。
了解iOS开发的同学都知道,苹果爸爸对应用上架的流程和审核效率实在让人无力吐槽,很多团队上一个版本还没审核结束,下一个版本就已经做好了。而React-Native支持从网络拉取JS,这样iOS应用也能够像Web一样实现快速迭代,然后绕过苹果爸爸的审核。
这个是react-native的调试过程。
最后简单说说单元测试,单元测试的姿势是对各个模块进行最小范围的测试。
以往对前端的UI进行单元测试,都需要依靠phantomjs、casperjs等沙盒浏览器内核,模拟用户操作,并检查操作后的DOM状态,比较繁琐,维护成本高得吓人。但有了React的虚拟DOM以后就简单多了。
我们来演示一个checkbox的单元测试过程。
因为虚拟DOM的存在,使得react的代码很容易做好单元测试,这是上面那段代码的测试用例,通过karma执行后即可看到结果。
当然,React的缺点也比较明显:
长路漫漫,唯剑作伴,一统江湖,匹夫有责。最后,祝大家早日占领终端和后台,走自己的路,让他们无路可走。
2016年11月18日-20日,由CSDN重磅打造的年终技术盛会SDCC 2016中国软件开发者大会将在北京举行,大会秉承干货实料(案例)的内容原则,本次大会共设置了12大专题、近百位的演讲嘉宾,并邀请业内顶尖的CTO、架构师和技术专家,与参会嘉宾共同探讨电商架构、高可用架构、编程语言、架构师进阶、微信开发、前端、平台架构演进、基于Spark的大数据系统设计、自动化运维与容器实践、高吞吐数据库系统设计要领、移动视频直播技术等。10月14日仍是五折抢票,最低1400元,注册参会。