React框架最早孵化于Facebook内部,Jordan Walke是框架的创始人。React作为内部使用的框架,在2011年的时候用于Facebook的新闻流(newsfeed),并于2012年用在了Instagram项目上。在2013年5月美国的JSConf大会上,Facebook宣布React框架项目开源。
图1-1为GitHub上React的开源项目截图,地址为: https://github.com/facebook/react/ 。
图1-1 GitHub上的React项目
React框架产生的缘由是在当时的技术背景下,前端MVC(Model-View-Controller)框架性能不能满足Facebook项目的性能需求以及扩展需求,所以Jordan Walke索性就自己着手开始写React框架,这种精神值得学习。
在当时Facebook内部极其复杂的项目中,面临的一个问题是,在MVC架构的项目中当Model和View有数据流动时,可能会出现双向的数据流动,那么项目的调试以及维护将变得异常复杂。
React官方也说自己不是一个MVC框架( https://reactjs.org/blog/2013/06/05/why-react.html ),或者说React只专注于MVC框架设计模式中的View层面的实现。
图1-2为React框架的基本结构,清晰明了地描述出了React底层与前端浏览器的沟通机制。
为了大大减少传统前端直接操作DOM的昂贵花费,React使用Virtual DOM(虚拟DOM)进行DOM的更新。
图1-2 React框架结构
React的组件是用户界面的最小元素,与外界的所有交互都通过state和props进行传递。通过这样的组件封装设计,使用声明式的编程方式,使得React的逻辑足够简化,并可以通过模块化开发逐步构建出项目的整体UI。
React框架中还有一个重要的概念是单向数据流,所有的数据流从父节点传递到子节点。假设父节点数据通过props传递到子节点,如果相对父节点(或者说相对顶层)传递的props值改变了,那么其所有的子节点(默认在没有使用shouldComponentUpdate进行优化的情况下)都会进行重新渲染,这样的设计使得组件足够扁平并且也便于维护。
以下的示例代码演示了React框架基本组件定义以及单向数据流的传递。
完整代码在本书配套源码的01-01-02文件夹。
我们在index.js文件中定义React项目的入口,render函数中使用了子组件BodyIndex,并通过props传递了两个参数,id和name,用于从父组件向子组件传递参数,这也是React框架中数据流的传递方式:
1. /** 2. * 章节: 01-01-02 3. * index.js 定义了 React 项目的入口 4. * FilePath: /01-01-02/index.js 5. * @Parry 6. */ 7. 8. var React = require('react'); 9. var ReactDOM = require('react-dom'); 10. import BodyIndex from './components/bodyindex'; 11. class Index extends React.Component { 12. 13. //生命周期函数 componentWillMount,组件即将加载 14. componentWillMount(){ 15. console.log("Index - componentWillMount"); 16. } 17. 18. //生命周期函数 componentDidMount,组件加载完毕 19. componentDidMount(){ 20. console.log("Index - componentDidMount"); 21. } 22. 23. //页面表现组件渲染 24. render() { 25. return ( 26. <div> 27. <BodyIndex id={1234567890} name={"IndexPage"}/> 28. </div> 29. ); 30. } 31. } 32. 33. ReactDOM.render(<Index/>, document.getElementById('example'));
在子组件BodyIndex中定义了state值,并通过setTimeout函数在页面加载5秒后进行state值的修改。页面表现层代码演示了如何读取自身的state值以及读取父组件传递过来的props值:
1. /** 2. * 章节: 01-01-02 3. * bodyindex.js 定义了一个名为 BodyIndex 的子组件 4. * FilePath: /01-01-02/bodyindex.js 5. * @Parry 6. */ 7. 8. import React from 'react'; 9. export default class BodyIndex extends React.Component { 10. constructor() { 11. super(); 12. this.state = { 13. username: "Parry" 14. }; 15. } 16. 17. render() { 18. setTimeout(() => { 19. //5秒后更改一下 state 20. this.setState({username: "React"}); 21. }, 5000); 22. 23. return ( 24. <div> 25. 26. <h1>子组件页面</h1> 27. 28. <h2>当前组件自身的 state</h2> 29. <p>username: {this.state.username}</p> 30. 31. <h2>父组件传递过来的参数</h2> 32. <p>id: {this.props.id}</p> 33. <p>name: {this.props.name}</p> 34. 35. </div> 36. ) 37. } 38. }
项目的package.json文件配置和使用的相关框架版本如下所示:
1. { 2. "name": "01-01-02", 3. "version": "1.0.0", 4. "description": "", 5. "main": "index.js", 6. "scripts": { 7. "test": "echo \"Error: no test specified\" && exit 1" 8. }, 9. "author": "", 10. "license": "ISC", 11. "dependencies": { 12. "babel-preset-es2015": "^6.14.0", 13. "babel-preset-react": "^6.11.1", 14. "babelify": "^7.3.0", 15. "react": "^15.3.2", 16. "react-dom": "^15.3.2", 17. "webpack": "^1.13.2", 18. "webpack-dev-server": "^1.16.1" 19. } 20. }
在命令行执行webpack-dev-server命令后,浏览器中的运行结果如图1-3所示,并且在5秒后子组件的state定义的username值由Parry变成了React。具体的配置方法及其意义将在后续章节讲解。
你可以直接在本地编写代码运行测试或直接下载本书配套源码运行,运行后,注意此state页面值更新的部分,整个页面没有进行任何的重新刷新加载,而只是进行了局部的更新,其原理详见下一节。
图1-3 代码在浏览器中的执行结果
React框架底层的核心为Virtual DOM,也就是虚拟DOM。本节将介绍它的底层特性,只有理解了React框架底层的本质,才能更好地帮助你理解React框架的前端表现,并为后续章节讨论React Native框架的性能优化进行一定的知识储备。
传统的HTML页面需要更新页面元素,或者说需要更新页面时,都是将整个页面重新加载实现重绘,执行这样的操作不管是从服务器代价还是从用户体验上来说,“代价”都是非常昂贵的。后来,有了AJAX(Asynchronous JavaScript And XML)这样的局部更新技术,实现了页面局部组件的异步更新,不过AJAX在代码的编写、维护、性能以及更新粒度的控制上还是不太完美。
文档对象模型(Document Object Model,DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口。在HTML网页上,将构成页面(或文档)的对象元素组织在一个树形结构中,用来表示文档中对象的标准模型就称为DOM。
React在框架底层设计了一个虚拟DOM,此虚拟DOM与页面上的真实DOM相互映射,当业务逻辑修改了React组件中的state部分,如上例中,子组件的state值,username由Parry修改成了React,React框架底层的diff算法会通过比较虚拟DOM与真实DOM的差异,找出哪些部分被修改了最终只更新真实DOM与虚拟DOM差异的部分。此计算过程是在内存中进行的,所以React在前端中的高性能表现正是来自于其底层的优良设计。
图1-4展示了React中的虚拟DOM与页面真实DOM之间的关系,其间的差异通过React框架底层的diff算法获取。
图1-4 React虚拟DOM与页面真实DOM
要更加深入地了解React在源码级别的实现原理,可以参考我博客里从React的源码角度对其底层批量更新state策略的分析文章:
http://blog.parryqiu.com/2017/12/19/react_set_state_asynchronously/
http://blog.parryqiu.com/2017/12/29/react-state-in-sourcecode/
http://blog.parryqiu.com/2018/01/04/2018-01-04/
http://blog.parryqiu.com/2018/01/08/2018-01-08/
通过以上对React框架的简介、代码演示以及底层原理的剖析得知,React最大的优势在于更新页面DOM时,对比于之前的前端更新方案,效率会大大提高。其实React并不会在state更改的第一时间就去执行diff算法并立即更新页面DOM,而是将多次操作汇聚成一次批量操作,这样再次大大提升了页面更新重绘的效率。
使用React框架开发,我们不会通过JavaScript代码直接操作前端真实DOM,而是完全通过state以及props的变更引起页面DOM的变更,相对于jQuery等框架那样进行大量的DOM查找与操作要简单、高效得多。
React框架在开源生态下,已经有大量的相关开源框架与组件可供使用,非常适合项目的快速开发。