我们先来直观认识React,对任何而一种工具,只有使用才能够熟练掌握,React也不例外。通过多React快速入手,我们会解析React的工作原理,并通过与功能相同的jQuery程序对比,从而看出React的特点:
如何初始化一个React项目
如何创建一个React组件
React的工作方式。
让我们开始旅程把!
1.1初始化一个React项目
为了开发React应用,你的电脑是运行微软Windows操作系统,还是苹果Mac,或者是linux,都不重要,只要保证具备以下条件:
安装了浏览器,如果是windows操作系统,请保证微软IE浏览器版本不低于8.0,因为React不支持比IE8更低版本的浏览器。
有一个命令环境,在Windows操作系统中有命令行界面,在苹果Mac电脑中可以使用Terminal应用,对于linux环境,命令行环境我想不用过多解释;
一个你最喜欢的代码编辑器,用于编辑React应用的代码,本教程注重实践,只有实际编码才能深入体会。
作为开发者,推荐使用谷歌Chrom浏览器,因为Chrom浏览器自带的开发辅助工具非常友好,而且还可以安装辅助React和Redux的扩展工具,具体的开发工具在第四章4.2中有详细介绍。
React是一个JavaScript语言的工具库,在这个JavaScript工具铺天盖地的时代,没有意外,你需要安装Node.js,React本身并不依赖与Node.js,但是我们开发中用到的诸多工具需要Node.js的支持。
在Node.js的官网(https://nodejs.org/)可以找到合适的安装方式,安装Node.js的同时也就安装了npm,npm是Node.js的安装包管理工具,因为我们不可能自己开发所有功能,会大量使用现有的安装包,就需要npm的帮助。
1.1.1create-react-app工具
React技术依赖与一个很庞大的技术栈,比如,转译JavaScript代码需要使用Babel,模块打包工具又要使用Webpack,定制build过程需要grunt或者gulp……这些技术栈都需要各自的配置文件,还没有开始写一行React相关代码,开发人员就已经被各种技术名词淹没。
针对这种情况,React的创建者Facebook提供了一个快速开发React应用工具,名叫create-react-app,这个工具的目的是将开发人员从配置工作中解脱出来,无需过早关注这些技术细节,通过创建一个已经完成基本配置的应用,让开发者快速开始React应用的开发。
本书中所有应用实例都有create-react-app创建,我们用这种最简单的方式创建可运行的运用,必要的时候才会介绍底层技术栈的细节,毕竟,没有声明比一个能运行的应用更加增强开发者的信心。
本书中所有应用实例都有create-react-app创建,我们用这种最简单的方式创建可运行的运用,必要的时候才会介绍底层技术栈的细节,毕竟,没有声明比一个能运行的应用更加增强开发者的信心。
crate-react-app是一个通过npm发布的安装包,在确认Node.js和npm安装好之后,命令行中执行下面的命令安装create-react-app:
npm install --global create-react-app
安装过程结束之后,你的电脑就会有create-react-app这样一个可以执行的命令,这个命令会在当前目录下创建指定参数名的应用目录。
我们在命令行中执行下面的命令:
create-react-app first_react_app
这个命令会在当前目录创建一个名为first_react_app的目录,在这个目录中会自动添加一个应用的框架,随后我们只需要在这个框架的基础上修改文件就可以开发React应用,避免了大量的手工配置文件。
在creat-react-app命令一大段文字输出之后,根据提示输入下面的命令:
cd first_react_app
npm start
这个命令启动一个开发模式的服务器,同时也会让你的浏览器自动打开了一个网页,指向本地地址http://localhost:3000/。
恭喜你,你的第一个React应用诞生了!
接下来,我们会用React开发一个简单的功能,让我们继续吧。
1.2增加一个新的React组件
React的首要思想是通过组件(Component)来开发应用。所谓组件,指的是能完成某个特定功能的独立的、可重用的代码。
基于组件的应用开发是广泛使用的软件开发模式,用分而治之的方法,把一个大的应用分解成若干小的组件,每个组件只关注于某个小范围的特定功能,但是把组件组合起来,就能够构成一个功能庞大的应用。如果分解功能的过程足够巧妙,那么每个组件可以在不同场景下重用,那样不光可以构建庞大的应用,还可以构建出灵活的应用。打个比方,每个组件是一块砖,而一个应用是一座楼,想要一次锻造创建一座楼是不现实的。实际上,总是锻造很多砖,通过排列组合这些砖,才能构建伟大的建筑。
React非常适合构建用户交互组件,让我们创建一个React组件开始。
学习任何一门语言或者任何一门课程,往往是从写Hello World程序开始,不过只是展示一句Hello World并不足以体现React的神奇能力,所以,我们要做一个不那么简单的组件,为了体现React对交互功能的支持,我们做一个显示点击次数的组件。
我们先看一看create-react-app给我们自动产生的代码,在first_react_app目录下包含如下文件和目录:
src/
public/ 、
README.md
package.json
node_modules
在开发过程中,我们主要关注src目录中的内容,这个目录是所有的源代码。
create-react-app所创建的应用入口是src/index.js文件,我们看看中间的内容,代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<App/>,
document.getElementById('root');
);
这个应用所做的事情,只是渲染一个名叫App的组件,App组件在目录下的App.js文件中定义。
我们要定义一个新的能够计算点击数组件,名叫ClickCounter,所以我们修改index.js文件如下:
import React from 'react';
import ReactDOM from 'react-dom';
import ClickCounter from './ClickCounter ';
import './index.css';
ReactDOM.render(
<ClickCounter />,
document.getElementById('root');
);
我们接下来会介绍代码的含义。
现在我们先来看看如何添加一个新组件,在src目录下增加一个新的代码文件ClickCounter.js,代码如下:
import React,{Component} from 'react';
class ClickCounter extends Component{
constructor(props){
super(props);
this.onClickButton = this.onClickButton.bind(this);
this.state = {count:0};
}
onClickButton(){
this.setState({count:this.state.count+1});
}
render(){
return(
<div>
<button onClick={this.onClickButton}>Clicki Me</button>
<div>
Click Count:{this.state.count}
</div>
</div>
)
}
}
export default ClickCounter;
如果你是从上一节不停顿直接读到这里,而且没有关闭命令行中的npm start命令,当你保存完这个文件之后,不需要主动做刷新网页的动作,就会发现网页中的内容已经发时改变。
去点击那个"Click Me"按钮,可以看到"Click Count"后面的数字会随之增加,每点击一次加一次。
恭喜你,现在你已经构建了一个有交互性的组件!
现在让我们来逐步解释代码中各部分的意义。
在index.js文件中,使用import导入了ClickCounter组件,代替了之前的App组件。
import ClickCounter from './ClickCounter ';
import是ES6(EcmaSript6)语法中导入文件模板的方式。ES6语法是一个大集合,大部分功能都被最新浏览器支持。不过这个import方法却不再广泛支持之列,这没有关系,ES6语法的JavaScript代码会被webpack和babel转译成所以浏览器都支持的ES5语法,而这一切无需开发人员做配置,create-react-app已经替我们完成了这些工作。
在ClickCounter.js文件的第一行,我们从react库中引入了React和Component,如下所示:
import React,{Component} from 'react';
Component作为所有组件的基类,提供了很多组件共有的功能,下面这行代码,使用的是ES6语法来创建一个叫ClickCouner的组件类,ClickCounter的父类就是Component:
class ClickCounter extends Component{
在React出现之初,使用的是React.createClass方式来创造组件类,这种方法已经被废弃了,但是在互联网上依然存在大量的文章来基于React.createClass来讲解React,这些文章中依然有很多真知灼见的部分,但是读者要意识到,使用React.createClass是一种过时的方法。在本教程中,我们只使用ES6的语法来构建组件类。
细心的读者会发现,虽然我们导入的Componnent类在ClickCounter组件定义中使用了,可是导入的React却没有被使用,难道在这里引入react没有必要吗?
事实上,引入react非常必要,你可以尝试删掉第一行中的React,在网页中会立刻出现错误信息。
这个错误信息的含义是:“在使用JSX范围内必须要有React。”
也就是说,在使用JSX的代码文件中,即使代码中并没有直接使用React,也一定要导入这个React,这是因为JSX会最终被转译成依赖于React的表达式。
接下来,我们就要认识什么是JSX。
1.2.1JSX
所谓JSX,是JavaCript的语法扩展(eXtension),让我们在JavaScrript中可以编写像HTML一样的代码。在ClickCounter.js的render函数中,就出现了类似这样的HTML代码,在indexjs中,ReactDOM.render的第一个参数<App/>也是一段JSX代码。
JSX中的这几段代码看起来和HTML几乎一模一样,都可以使用<div><button>之类的元素,所以只要熟悉HTML,学习JSX完全不成问题,但是,我们一定要明白两者的不同之处
首先,在JSX中使用的“元素”,不局限与HTML中的元素,可以是任何一个React组件,在App.js中可以看到,我们创建的ClickCounter组件直接被直接应用到JSX中,使用方法喝其他元素一样,这一点是传统的HTML做不到的。
React判断一个元素是HTML元素还是React组件的原则就是看第一个字母是否是大写,如果在JSX中我们不用ClickCounter而是用clickCounter,那就得不到我们想要的结果。
其次,在JSX中我们可以通过onClick这样的方式给一个元素添加一个事件处理函数,当然,在HTML中也可以用onclick,但在HTML中直接书写onclick一直就是为让诟病的写法,网页应用开发界一直倡导的是用jQuery的方法添加事件处理函数,直接写onclick会带来代码混乱的问题。
这就带来一个问题,既然长期一直不倡导在HTML中使用onclick,为什么在React的JSX中我们却要使用onClick这样的方式来添加事件处理函数呢?
1.2.2JSX是进步还是倒退
在React出现之初,很多人对React这样的设计非常反感,因为React把类似HTML的标记语言和JavaScript混在一起了,但是,随着时间的推移,业界逐渐认可了这种方式,因为大家发现,以前用HTML来代表内容,用CSS代表样式,用JavaScript来定义交互行为,这三种语言分在三种不同的文件里面,实际上是把不同的技术分开管理了,而不是逻辑上的“”分而治之“”。
根据做同一件事的代码应该有高耦合性的设计原则,既然我们要实现一个ClickCounter,那为什么不把实现这个功能的所有代码集中在一个文件里呢?
这点对于初学者可能有点难以接受,但是相信你在看完这个教程后,观点会随之改变。
那么,在JSX中使用onClick添加事件处理函数,是否代表网页应用开发兜了一个大圈,最终回到了起点了呢?
不是这样,JSX的onClick事件处理方式和HTML中直接使用onclick有很大不同。
即使现在,我们还是要说在HTML中直接使用onclick很不专业,原因如下:
️①onclick添加的事件处理函数是在全局环境下执行的,这污染了全局环境,很容易产生意料不到的后果;
②给很多DOM元素添加onclick事件,可能会影响网页的性能,毕竟,网页需要的事件处理函数越多,性能就会越低。
③对于使用onclick的DOM元素,如果要动态地从DOM树中删掉的话,需要把对应的事件处理器注销,假如忘了注销,就可能造成内存泄露,这样的bug很难被发现。
上面说的这些问题,在JSX中都不存在。
我们在JSX中看到一个组件使用了onClick,但并没有产生直接使用 onclick(注意是onclick不是onClick)的HTML,而是使用了事件委托(event delegation)的方式处理点击事件,无论有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最顶层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数,使用事件委托的性能当然要比为每个onClick都挂载一个事件处理函数要高。
因为React控制了组件的生命周期,在unmount的时间自然能够清除相关的所有事件处理函数,内存泄露也不再是一个问题。
除了在组件中定义交互行为,我们还可以在React组件中定义样式,我们可以修改ClickCounter.js中的render函数,代码如下:
render(){
const counterStyle={
margin:'16px'
}
return (
<div style={countStyle}>
<button onClick={this.onClickButton}>Click Me</button>
<div>
Click Counter:<span id="clickCount">{this.state.count}</span>
</div>
</div>
);
}
我们在JavaScript代码中定义一个counterStyle对象,然后在JSX中复制给顶层div的style属性,可以看到这个网页总这个部分的margin真的变大了。
你看,React的组件可以把JavaScript、HTML和CSS的功能在一个文件里,实现真正的组件封装。
1.3分解React应用
前面我们提到过,React应用实际上依赖于一个很大很复杂的技术栈,我们使用create-react-app避免在一开始就费太多精力配置技术栈,不过现在是时候了解一下这个技术栈了。
我们启动React应用的命令是npm start,看一看package.json中对start脚本的定义,如下所示:
“scripts":{
"start":"react-scripts start",
"build":"react-scripts build",
"test":"react-scripts test --env=jsdom",
"eject":"react-scripts eject"
}
可以看到,start命令实际上是调用了react-scripts这个命令,react-scripts是create-react-app添加的一个npm包,所有的配置文件都藏在node_modules/react-scripts目录下,我们当然可以钻进这个目录去一探究竟,但是也可以使用eject方法来看清楚背后的原理。
你可以发现package.json文件和start并列还有其他几个命令,其中build可以创建生产环境优化代码,test用于单元测试,还有一个eject命令很有意思,这个eject(弹射)命令做的事情,就是把潜藏在react-scripts中的一系列技术栈配置都“”弹射“”到应用的顶层,然后我们就可以研究这些配置细节了,而且可以更灵活地定制应用的配置。
注意:eject命令是不可逆的,所以,当执行eject之前,最好做一下备份。
我们在命令行执行下面的命令,完成“”弹射“”操作:
npm run eject
这个命令会改变一些文件 ,也会添加一些文件。
当前目录下会增加两个目录,一个是scripts,同时,package.json文件中的scripts部分发生了变换:
“scripts":{
"start":"node scripts/start.js",
"build":"node scripts/build.js",
"test":"node scripts/test.js --env=jsdom"
}
从此之后,start脚本将使用scripts目录下的start.js,而不是弄得_modules目录下的react-scripts,弹射成功,再也回不去了。
在config目录下的webpackage.config.dev.js文件,定制的就是npm start所做的构造过程,其中有一段关于babel的定义:
{
test:/\.(js|jsx)$/,
include:paths.appSrc,
loader:'babel',
query:{
cacheDirectory:true
}
},
代码中paths.appSrc的值就是src,所以这段配置的含义指的是所有以js或者jsx为扩展名的文件,都会由babel所处理。
并不是所有的浏览器都支持所有ES6语法,但是有了babel,我们就可以不用顾忌太多,因为babel会把ES6语法的JavaScript代码转译(trranspile)成浏览器普遍支持的JavaScript代码,实际上,在React社区,不使用ES6语法写代码才显得奇怪。
1.4React的工作方式
在继续深入学习React的其他知识之前,我们先就这个简单的ClickCounter组件思考一下React的工作方式,要了解一样东西的特点,最好的方法就是拿这个东西和另一样东西作比较。我们就拿React和jQuery来比较。
1.4.1Jquery如何工作
假设我们用jQuery来实现ClickCounter的功能,该怎么做呢?首先,我们要产生一个网页的HTML,写一个index.html文件如下所示:
<!DOCTYPE html>
<html>
<body>
<div>
<button id="clickMe">Click Me</button>
<div>
Click Count:<span id="clickCount">0</span>
</div>
</div>
<script src="../jquery-1.9.1.min.js">
<script src="./clickCounter.js"></script>
</body>
</html>
实际产品中,产生这样的HTML可以用PHP、Java、Ruby on Rails或者任何一种服务器端语音和框架来做,也可以在浏览器中使用Mustache、Hogan这样的模板产生,这里我们只是把问题简化,直接书写HTML。
上面的HTML只是展示样式,并没有任何交互功能,现在我们用jQuery来实现交互功能,和jQuery的 传统一样,我们把JavaScript代码写在一个独立的文件clickCounterr.js里面,如下:
$(function(){
$('#clickMe').click(function(){
var clickCounter = $('#clickCount');
var count = parseInt(clickCounter.text(),10);
clickCounter.text(count+1);
})
})
用浏览器打开上面创造的index.html,可以看到实际效果和我们写的React应用一模一样,但是对比这两段程序可以看出差异。
在jQuery的解决方案中,首先根据CSS规则找到id为clickCounter的按钮,挂上一个匿名事件处理函数,在事件处理函数中,选中那个需要被修改的DOM元素,读取其中的文本值,加以修改,然后修改这个DOM元素。
选中一些DOM元素,然后对这些元素做一些操作,这是一种最容易理解的开发模式。jQuery的发明人John Resig就是发现了网页应用开发者的这个编程模式,才创造出了jQuery,其一问世就得到普遍认可,因为这种模式直观易懂5。但是,对于庞大的项目,这种模式会造成代码结构复杂,难以维护,每个jQuery的使用者都会有这种体会。
1.4.2React的理念
与jQuery不同,用React开发应用是另一种体验,我们回顾一下,用React开发的ClickCounter组件并没有像jQuery那样做“选中一些DOM元素然后做一些事情”的动作。
React的理念,归结为一个公式,就像下面这样:
UI=render(data)
让我们来看看这个公式表达的含义,用户看到的界面(UI),应该是一个函数(在这里叫render)的执行结果,只接受数据(data)作为参数。这个函数是一个纯函数,所谓纯函数,指的是没有任何副作用,输出完全依赖于输入的函数,两次函数调用如果输入相同,得到的结果也绝对相同。如此一来,最终的用户界面,在render函数确定的情况下完全取决于输入数据。
对于开发者来说,重要的是区分分开哪些属于data,哪些属于render,想要更新用户界面,要做的就是更新data,用户界面自然会做出响应,所以React实践的也是“响应式编程“(Reactive Programming)的思想,这也就是React为什么叫做React的原因。
1.4.3Virtual DOM
既然React应用就是通过重复渲染来实现用户交互,你可能会有一个疑虑:这样的重复渲染会不会效率太低了呢?毕竟,在jQuery的实现方式中,我们可以清楚地看到每次只有需要变化的那一个DOM元素被修改了;可是,在React的实现方式中,看起来每次render函数被调用,都要把整个组件重新绘制一次,这样看起来有点浪费。
事实并不是这样,React利用Virtual DOM,让每次渲染都只重新渲染最少的DOM元素。
要了解Virtual DOM,就要先了解DOM,DOM是结构化文本的抽象表达式,特定于web环境中,这个结构化文本就是HTML文本,HTML中的每个元素都对于DOM中某个节点,这样,因为HTML元素的逐级包含关系,DOM节点自然就构成了一个树形结构,称为DOM叔5。
浏览器为了渲染HTML格式的网页,会先HTML文本解析以构建DOM树,然后根据DOM树渲染出用户看到的界面,当要改变界面内容的时候,就去改变DOM树上的节点。
Web前端开发关于性能优化有一个原则:尽量减少DOM操作。虽然DOM操作也只是一些简单的JavaScript语句,但是DOM操作会引起浏览器对网页进行重新布局,重新绘制,这就是一个比JavaScript语句执行慢很多的过程。
虽然JSX看起来很像是一个模板,但是最终会被Babel解析为一条条创建React组件或者HTML元素的语句,神奇之处在于,React并不是通过这些语句直接构建DOM树,而是首先构建Virtual DOM。
既然DOM树是对HTML的抽象,那Virtual DOM就是对DOM树的抽象。Virtual DOM不会触及浏览器的部分,只是存在于JavaScript空间的树形结构,每次自上而下渲染React组件时,会对比这一次产生的Virtual DOM,对比就会发现差别,然后修改真正的DOM树时就只需要触及差别中的部分就行。
以ClickCounter为例,一开始计数为0,用户点击按钮让点击计数变成1,这一次重新渲染,React通过Virtual DOM的对比发现其实只是id为clickCount的sapn元素中内容从0变成了1而已:
<span id="clickCount">{this.sate.count}</span>
React发现这次渲染要做的事情就是更换这个span元素的内容而已,其他DOM元素都不需要触及,于是执行类似写了的语句,就完成了任务:
document.getElementById("clickCount").innerHTML="1";
React对比Virtual DOM寻找差异的过程比较复杂,后面第五章我们会详细介绍对比的过程。
1.4.4React工作方式的优点
毫无疑问,jQuery的方式直观易懂,对于初学者十分适用,但是当项目逐渐变得庞大时,用jQuery写出的代码往往互相纠缠,难以维护。
使用React的方式,就可以避免构建这样复杂的程序结构,无论何种事件,引发的都是React组件的重新渲染,至于如何只修改必要的DOM部分,则完全交给React去操作,开发者并不需要去关心。
React利用函数式编程的思维来姐姐用户界面渲染的问题,最大的优势是开发者的效率会大大提高,开发出的代码可维护性和可读性也大大增强。
React等于强制所有组件都按照这种由数据驱动渲染的模式来工作,无论应用的规模多大,都能让程序处于可控范围内。
1.5本章小结
在这一章里,我们用create-react-app创造了一个简单的React应用,在一开始,我们就按照 组件的思想来开发应用,React的主要理念之一就是基于组件来开发应用。
通过和同样功能的jQuery实现方式对比,我们了解了React的工作方式,React利用声明式的语法,让开发者专注于描述用户界面“”显示成什么样子“”,而不是重复思考“”如何去显示“”,这样可以大大提高开发效率,也让代码 更加容易管理 。
虽然React是通过重复渲染来实现动态更新效果,但是借助Virtual DOM技术,实际上这个过程并不牵扯太多的DOM操作,所以渲染效率更高。
更多内容,请访问的我的个人博客:[https://liugezhou.github.io/blog](https://liugezhou.github.io/blog)
您也可以关注我的个人公众号:【Wakaka】