React是最近一段时间被讨论的最多的前端话题之一。本文就来谈一谈React的入门实践, 本文会涉及到React开发中的一些常用概念,并且提供一些笔者认为的最佳入门实践,仅供参考。
首先值得注意的是,React并不是一个框架,它提供了一些新颖的概念、库、 和编程原则来让你能够同时在服务端和客户端编写快速、紧凑、漂亮的代码, 它能够让你方便快速的构建web应用的表现层(View)。
如果你准备使用React,那么可能会涉及到一些常用的概念或技术,包括:
ES6 React
虚拟DOM(virtual DOM)
组件驱动开发(component-driven development)
不变性(immutability)
自上而下的渲染(top-down rendering)
渲染路径和优化
打包工具, ES6, 构建请求, debugging, 路由等
同构React(isomorphic React)
什么是React.js
React.js不是一个框架
在整个Web应用的MVC架构中,你可以将React看作为视图层,并且它是一个高效的视图。 React提供了和以往不一样的方式来看待视图,它以组件开发为基础。对React应用而言, 你需要将你的页面分割为一个个的组件。也就是说,开发页面的过程编程了组件开发的过程, 而页面就是组件的拼装结果。
通过分割组件的方式,你能够控制页面的复杂性,通过组件的方式去封装复杂的页面或某个功能区块。 并且,组件是可以被复用的。你可以将这个过程想象为:用乐高积木去拼装不同的物体。 因而,我们称这种编程方式称为组件驱动开发。
React的另一大特点是其所拥有的虚拟DOM。通过虚拟DOM,页面渲染变得更加高效,并且, DOM操纵也变得更加可控。基于这两点,React所构建的视图层具有强大的自上而下的页面渲染能力。
总结一下:React有两个主要特点:组件化和高效的虚拟DOM。但是为什么它这么被看好呢? 因为React更多的是一种概念层面的东西,而所提供的库是其次的。 也有很多其他遵从了这些思想的第三方实现。和每一个编程概念一样, React有其独有的解决方案、工具和工具。这里我们并不会去深入的讨论这些内容, 而是关注React本身。
Virtual DOM
为了跟踪模型层的变化,并且将其应用到DOM中(也就是渲染),我们需要关注两件重要的事:
数据是什么时候改变的
哪一个(些)DOM元素需要被更新
对(1)而言,React提供了一个观察者模型用于替代传统的脏检查(dirty checking)机制, 也就是说它会持续的检查模型的变化。 这也就是解释了为什么React不需要计算哪些元素发生了改变的原因,因为它能立即知道。 这个过程减少了计算量,并且能够让web应用的运行变得更平滑。但真正有趣的还在于: React是如何管理DOM操纵的。
对于DOM改变(2)而言,React在内存中构建了DOM的树形表示,随着模型的变化, 它会计算出哪个DOM元素应该被改变。对浏览器而言,DOM操纵是比较耗费性能的, 因此我们更倾向于将这个操作变得最少化。因此,基于这个思路, React视图尽可能少的触及到DOM元素。对于对象表示而言, 更少的DOM操纵意味着计算会更快,因此DOM改变也被尽可能的减少。
React在底层实现了一个diffing
算法,该算法使用DOM的树形表示法,当某个 节点发生变化(标记为dirty)时它会重新计算整个子树,你会注意到你的模型发生 了改变,因为整个子树在之后会被重新渲染。关于该算法的详细分析可以参考这篇 文章。
如何在服务端渲染
因为React在DOM表示时使用了一个虚拟(假的)DOM,因此借助于这种方式使得在服务端 渲染输出HTML称为可能(不借助于JSDom, PhantomJS等)。 React还能智能的识别出服务端渲染出来的页面标记,并在客户端只为这些标记添加事件处理器, 这对构建同构Web App非常有用。
有意思的是,React渲染出来的HTML标记都包含了data-reactid
属性, 这有助于在React中追踪DOM节点。
一些阅读资料
React’s diff algorithm
The Secrets of React’s virtual DOM
Why is React’s concept of virtual DOM said to be more performant than dirty model checking?
virtual-dom
组件驱动开发 Component-Driven Development
对于组件驱动开发而言,你在一个模板中是看不到整个网站(应用)的。使用这种新的开发思路, 在一开始你可能会碰到一些苦难,但是如果能够深入的实践下去,应用开发将变得更加易于理解、 易于维护、并且更容易被测试。
如何使用React的方式来思考组件开发
下面我们来讨论如何实践组件驱动开发这一理念。首先看一个例子,这个例子来源于 thinking in react 这篇文章。对于构建一个可过滤的产品列表而言,假设其包括如下的组件结构:
FilterableProductTable 容器
ProductCategoryRow 产品类别栏
ProductRow 产品行
SearchBar 搜索栏
ProductTable 产品表
一个组件应该包含什么
理想状态下,我们应该遵守单一责任原则 来设计组件。当你认为你的组件需要做更多事情的时候,你可以再考虑将其分割为更小的组件集合。
组件可以进行嵌套,也就是说,在你的组件中也可以使用到其他组件。 首先我们来看下试用ES5来开发一个React组件的基础代码:
如果使用ES6,你的组件代码可以这样写:
使用ES6,代码会变得更加清晰。
JS和JSX
正如你所看到的,我们的组件是JS和HTML代码的混合,你可能会觉得这样做很糟糕, 因为MVC一直在教我们尽可能的隔离视图和控制逻辑。但是,通过代码混写, 我们能够达到另一种层面的单一责任,因为它能让你的组件变得更加灵活和可重用。
当然,在React中你也可以使用纯JS来编写你的组件:
是的,你会发现这很麻烦,没有使用HTML来得直观。因此React提供了JSX (JavaScript eXtension)语法让你能够在JS中书写HTML标记。
什么是JSX
JSX在ECMAScript的基础上提供了类似于XML的扩展。 JSX和HTML有点像,但也有不一样的地方。例如,HTML中的class
属性在JSX中为className
。 其他不一样的地方,你可以参考FB的HTML Tags vs. React Components这篇文章。
显然,浏览器原生并不支持JSX,因此我们需要某种手段将其转换为JS代码, 有很多工具能够帮你完成这个任务,后面我们会有介绍。一个流行的选择是使用Babel, 它能够将JSX转换为JS代码。
一些参考资料:
JSX in depth
Online JSX compiler
Babel: How to use the react transformer
组件还应该包括什么
组件还应该包括一些内部状态,处理逻辑,和事件处理器(例如按钮点击、输入改变)等, 当然也包括一些内部的样式。
你会遇到{this.props.name}
这样的代码片段,这意味着你可以通过属性的方式向组件内传递数据, 例如<MyComp name='weiwei sun' />
。这让组件变得可重用, 并且能够自上而下的向嵌套的组件传递数据。
示例代码如下:
React拥抱ES6
在React中尝试编写ES6是个非常不错的开始,React并不是一开始就支持ES6的, 而是从v0.13.0
开始支持的。你可能会经常用到的ES6特性包括类、箭头函数、const和模块。 例如,我们会经常从继承React.Component
类开始编写我们的组件。
还有一点需要注意的是,并不是每个浏览器都支持ES6,因此目前情况下, 我们需要使用一些工具将我们编写的ES6代码转换为ES5代码, 我推荐使用Babel。
一些参考资料:
Babel: Learn ES6
React ES6 announcement
组件生命周期
每个React组件在加载时都有特定的生命周期,在此期间不同的方法会被执行。 下面简单介绍React组件的生命周期:
componentWillMount
该方法会在组件render
之前执行,并且永远只执行一次。
componentDidMount
该方法会在组件加载完毕之后立即执行。此时,组件已经完成了DOM结构的渲染, 并可以通过this.getDOMNode()
方法来访问。
componentWillReceiveProps
组件接收到一个新的prop时会被执行,且该方法在初始render
时不会被调用。
shouldComponentUpdate
在组件接收到新的props或state时被执行。
componentWillUpdate
在组件接收到新的props或者state但还没有render时被执行。 在初始化时不会被执行。
componentDidUpdate
在组件完成更新后立即执行。在初始化时不会被执行。 一般会在组件完成更新后被使用。
componentWillUnMount
在组件从DOM中unmount后立即执行。该方法主要用来执行一些必要的清理任务。
关于生命周期的具体内容,你可以参考官方文档。
在打包时使用Webpack和Babel
在开发时,我们会经常用到一些工具。最常用到的是是Node.js
的模块系统和它的包管理工具NPM
。 在React应用开发中,我们通常会用Node风格的require
语句来引入我们需要的NPM模块。值得一提的是, React本身也是一个独立的NPM模块。
在模块化方面,你通常会有两种选择,CommonJS或者ES6:
或
其他方面,我们还会使用debug模块来调试, 使用superagent模块来编写请求。
现在,我们有了Node的依赖管理系统,并且使用npm
来提供模块。下面我们需要做的 事:选择一个合适的工具来打包我们的代码,并且能够让其运行在浏览器上。
因此我们还需要一个打包器。目前最流行的解决方案包括两个,分别是Browserify和 Webpack。我们选择使用Webpack,因为Webpack更适合于React社区。
Webpack是如何工作的
Webpack用于打包我们的代码,并且包含进我们需要的包,然后输出为浏览器可运行的文件。 因为我们使用JSX
和ES6
,因此我们需要相应的工具来将其转换为ES5。事实上, Babel能够同时做这两件事。使用Webpack能够很轻松的完成这些任务,因为Webpack是面向配置的。
使用如下命令开始:
然后创建webpack.config.js
文件,我们需要使用ES5来编写该文件,因为它是 webpack的配置文件。一个典型的配置方式如下:
运行webpack
命令你可以执行打包流程。这之后你可以只在页面中包含bundle.js
即可。 如下:
提示:你可以使用
node-static
来存放你的静态资源文件,使用npm install -g node-static
来安装,并使用static .
来启动。
项目结构
一个典型的项目结构你可以参考这个仓库。
DEMO:https://github.com/RisingStack/react-way-getting-started
如何测试React组件
对于React组件的测试,这里推荐使用Jest, Jest也是由Facebook提供的测试框架,并且有很多强大的特性,但这里并会详细的 介绍它们。
关于Jest,我推荐你阅读和尝试来自Facebook的Tutorial。
对于ES6代码的测试,你可以参考 React ES6 Testing。
小结
本文简单介绍了React的基础原理,一些相关的编程技术。后续还会整合一些资料谈一谈Flux和同构。