React一小时入门
2016-08-12
iostreaminReact是Facebook开发的js库,不仅影响了其他前端库的开发思路,而且还引申出React Native等技术,在开源世界引起了极大的反响。Facebook为什么要花费大量的精力开发React,为了解决什么问题,以及如何解决问题,我们将就这些问题做一些简单的讨论和学习。React背景Facebook在开发广告系统时发现,因为他们非常庞大的代码库,导致前端的MVC架构非常复杂难以维护。每当开发新需求时,系统复杂度成倍增长,代码非常脆弱且执行结果不可预测。所以Facebook认为MVC架构不适合开发大规模的前端应用,其中很重要的原因是应用中的模型(M)和视图(V)之间的双向数据绑定导致前端代码复杂度迅速提高,难以理解和调试,极大地影响Facebook的开发效率。
Facebook给出的解决方案就是React。React在Facebook内部已经试用了多年,效果很好。React另辟蹊径解决前端代码复杂度高的问题,JSX语法甚至被初学者认为不伦不类。但是我在尝试了React之后,我无法自拔地喜欢上了这种单向数据驱动的开发思路。那么React是解决什么问题的呢,Facebook官网上介绍说:We built React to solve one problem: building large applications with data that changes over time.即React是用来构建那些数据会随时改变的大型应用。为了构建大型应用,React有两个主要的特点:简洁代码里非常简单的描述在每个时间点应用应该呈现的样子。当应用数据改变时,React会自动管理UI界面。声明式当数据发生改变时,React表现的是刷新DOM树。但事实上React仅仅更新发生了变化的那一部分。我们要做的就是构建组件(Component),封装组件。React自动管理组件的生命周期。React特性React有三大特性:组件化、虚拟DOM和单向数据流。这三大特性是React运行的基础,并且由虚拟DOM衍生出React Native项目。组件化React允许将代码封装成组件(Component),然后像插入普通HTML标签一样,在页面中插入这个组件(源码参见:https://github.com/xeostream/react-demo/blob/master/helloworld.html)。上述代码中,变量HelloMessage就是一个组件类。所有的组件类必须有render方法,用于输出组件内容。组件的用法与原生的HTML标签完全一致,可以任意加入属性。
比如,就是在HelloMessage组件中加入message属性,值为“yo,what'up,man?”。组件内部可以通过this.props对象获取组件的属性,this.props.message就是取message属性值。上面代码的运行结果如下。组件化的最显著特征就是万物皆由组件构成,那么多个组件之间相互通信就是很大的问题。一般解决方式有三种:使用props,构建通信链在组件初始化时,保存组件的句柄。在其他组件中使用句柄达到直接访问组件的目的,完成通信使用PubSub模式首先第一种方法容易理解,但是在组件嵌套较深的情况下,为达到通信的目的,组件之间相互调用而且组件需要冗余许多不需要的props,不太适合;第二种方法避免了第一种方法的问题,但是需要维护很多变量,也不是非常好的方案;对于第三种方法,PubSub模式有助于组件解耦和代码组织,而且PubSub有很多开源实现。建议组件间通信使用PubSub模式。虚拟DOM当组件状态改变的时候,React会自动调用组件的render方法重新渲染整个组件的UI。但是如果这样大面积的操作DOM,性能会是一个很大的问题,所以React实现了虚拟DOM。虚拟DOM是一个纯粹的JS数据结构,存到内存中,性能很快。React将组件的DOM结构映射到虚拟DOM上,在虚拟DOM上实现了一个高效的diff算法。所以每次当组件的数据更新时,React会通过diff算法找到需要更新的DOM节点,再把修改更新到浏览器实际的DOM节点上。单向数据流单向数据流是React推崇的一种应用架构的方式。在React的组件中,我们监听状态的变化,并在组件的声明周期函数里对组件状态做一个的响应和操作,即页面的变化只与状态数据的变更有关。这里展示一种官方的单向数据流实现:(注:Flux由单向数据流扩展而来,React与Flux相互独立,React仅实现Flux架构中的View部分。当然也可以使用其他js库实现这个View。)这里我们将React组件理解为一个状态机,状态机内部的状态发生了改变,则对外的输出也会发生改变,两者之间的关系是一一对应的,即如果组件的状态数据是确定的话,则组件的输出也是确定的。这点对于前端的测试和DEBUG是非常大的帮助,对减少前端BUG是很有好处的。我开始不太理解单向数据流的概念,因为搞不清楚单向数据流和MVC的关系。其实单向数据流并不是和MVC在同一层次对系统的抽象,单向数据流表达的是MVC中View和Model之间数据的传递方式。所以这个问题更精确的表达应该是单向数据流和双向数据流之间的对比。双向数据流常见于Angular1.x等库中,指Model和View可以相互传递数 据,且多个Model和View之间传递没有限制,其中传递是代码不需要显式设置监听事件以同步数据,而是Model和View相互绑定。所以双向数据流的优势是容易理解,在简单系统中开发非常方便;但是缺点是在复杂工程中,多个Model和View之间绑定,保持这种绑定关系极耗性能,经常会导致View无响应,性能急剧下降,而且因为存在多层绑定关系,导致View的Debug几乎不可能。基于以上原因,很多前端框架如angular2.0已经开始将单向数据流作为默认的绑定方式。React原理上面一直在说React是要解决什么问题的,现在说下React是怎么解决这些问题的?传统的web应用,操作DOM一般是直接更新操作或者是大面积页面刷新,这种操作是比较昂贵的。React为了尽量减少对DOM的操作,提供了一种与众不同的方式来更新DOM。就是DOM层之前增加一个轻量级的虚拟DOM,虚拟DOM是React抽象出来的描述真实DOM结构的对象,由虚拟DOM管理真实DOM的更新。虚拟DOM为保证高效的更新真实DOM,在更新之前增加diff算法计算出真实DOM的最小变更。在真实DOM树上的节点被称为元素,在虚拟DOM里被称为组件。组件是非常重要的,虚拟DOM是由组件组成。 component 的使用在 React 里极为重要, 因为 components 的存在让计算 DOM diff 更高效。state、props和render在组件中是非常重要的属性。state、props属性包含定义组件需要的数据。state表示组件当前的状态,当state发生变化时,组件会调用render方法重新渲染。相对于state,props是组件初始化需要的数据,React规约props在组件的生命周期内无法更改也不应改变。在组件的生命周期中,随着该组件的props和state发生改变,组件的DOM表现也会有相应的变化。一个组件即是一个状态机,对于特定地输入,组件总返回一致的输出。组件的生命周期可以分为三大过程。分别为:mounted组件被渲染为真实的DOM元素插入浏览器的DOM结构的一个过程。update已经处于mounted状态的组件被重新渲染的过程。unmounted处于mounted状态的组件被从浏览器DOM结构中移除的过程。组件的完整生命周期如下:在组件的生命周期中有几个比较重要的方法:getDefaultProps此方法返回的对象可以用于设置默认的props值。getInitialState此方法用来初始化组件实例的state,在这个方法里可以访问组件的props变量。componentWillMount此方法在组件首次渲染之前调用,是在调用render方法之前最后一次修改组件的机会。render此方法会创建一个虚拟DOM,用来表示组件的输出。在组件中,render方法是必须的方法。render方法本身需要满足几点:只能通过this.props和this.state访问组件的数据可以返回null,false或者任何React组件返回结果只能有一个顶级组件,不能返回一组元素componentDidMount此方法在render方法执行之后被调用,所以在方法中可以获取组件在真实DOM的节点。shouldComponentUpdate此方法决定组件是否重新渲染,如果方法返回结果为false的话,则组件不会调用render方法重新渲染。componentWillUpdate此方法和componentWillMount类似,渲染后的组件在接收到新的props或者state改变之后,组件会调用此方法。componentDidUpdate组件因接收到新的props或者state改变导致的重新渲染之后,会调用此方法,所以可以在方法中访问或者修改真实DOM。componentWillUnmount当组件从DOM中卸载后销毁,会调用此方法完成所有的清理和销毁工作。在conponentDidMount中添加的任务都需要在此方法中销毁,比如创建的定时器和事件监听器。React示例计时器(源码参见:
https://github.com/xeostream/react-demo/blob/master/timer.html)执行结果(原本想做个GIF,然而并不会做。。。):React体验使用单向数据流可以很好的隔绝业务,大大降低了单元测试的难度。尽量将React组件(Component)做到小,做到细,也就是尽量拆分React组件。基于数据驱动的方式开发,虽然开始的时候不容易理解,但确实可以减少前端的BUG。React更适合数据驱动的项目,不太适应交互比较多的项目。作者:王建双,就职于开鑫贷,主要负责Java Web方向的开发工作,也会根据兴趣涉猎前端相关的开发技术。目前在学习React和React Native、Spring高级特性。