一、什么是 React?
React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
React 拥有 声明式、组件化,一次学习随处编写等特点。
React 应用的核心是组件,组件实际上是一段用户界面的代码, 我们将创建一组独立、可重用的组件来组合成复杂的用户界面,每个 react 程序至少有一个组件也就是 root 根组件,这个组件代表了整个程序,并且包含了很多的子组件。
React 使用渲染方法 render() 来渲染元素,也就是在 JavaScript 中对于的 DOM 元素,react 在内存中保持着一个轻量级的 DOM 元素树,也就是我们常说的虚拟 DOM,当我们改变一个组件的状态时,我们就得到了一个新的 react 元素,react 会将之前的组件和子组件进行比较,对比差异后更新浏览器中的 DOM,以确保信息保持同步。
在使用 React 创建程序时不同于传统的 JavaScript 库比如 jQuery,我们不直接操作浏览器的真实 DOM 元素,我们不必为了捕获 DOM 元素写代码,然后处理 DOM 的请求,而是只需要简单的改变组件的状态,之后 react 将会自动检测并修改 DOM 以同步状态,这也就是为何这个库称为 React (反应),因为当我们状态改变的时候,react 是真的’反应过来’状态已经改变,并且去更新 DOM。
我们经常会被问到一个问题是关于 Angular 与 React 的,它们都是基于组件逻辑的架构,但不同的是 Angular 是一个框架,或者说是一个完整的解决方案。而 React 只是只是一个库,只关注显示部分的东西,并且确保视图始终同步,所以 react 需要学习的接口很少,我们为了实现目标还要借助其他的库,比如 Routing 或处理 HTTP 请求,这相比 Angular,我们要自由很多我们可以选择自己喜欢的库。
二、开发环境
请先确保您安装了较新版本的 Node.js,我们不会用到 Node.js 但会使用内置工具也就是包管理工具 npm,来安装第三方库。
1 | # 查看Node.js版本 |
我们先来安装 create-react-app 脚手架工具类库来创建 react 项目。
1 | # npm 是我们要执行的包管理工具 |
如果是 Mac 或 Linux 系统需要配置权限,在前面添加 sudo 关键字会使命令在管理员权限下运行
1 | sudo npm i -g create-react-app |
我在此使用微软的 Visual Studio Code 编辑器来进行开发,因为它是一个漂亮的轻量级和跨平台的编辑器。我将要安装两个扩展,这样开发起来比较方便。
首先是 Simple React Snippets 是由 Burke Holland 开发的,这个扩展已经有 345,580 安装了,是个很受欢迎的扩展,它是用来可以让我输入缩写然后自动生成代码的工具,安装完毕后需要重启 VS Code。
下一个是 Prettier - Code formatter 是由 Esben Petersen 开发的,这个扩展已经有 13,842,180 安装了,我们使用这个扩展来进行格式化我们的代码,我们可以开启保存时格式化的设置,当我们保存时这个扩展将自动格式化你的代码。
我们打开 文件 -> 首选项 -> 设置 或者直接 ctrl + , 打开设置页面,我们可以搜索 formatOnSave 或者在 文本编辑器 -> 正在格式化 中的 Format On Save 项勾选之后,就可开启保存时自动格式化的功能。
使用 Prettier - Code formatter 插件时默认会将单引号转换为双引号,可在根目录中 创建 .prettierrc.json 文件将 singleQuoto 设为 true,即可将双引号改为单引号,semi 改为 false 会将多余的分号去掉。
1 | { |
三、第一个 React 应用
我们使用脚手架创建一个 React 应用程序,我们在控制台中输入 create-react-app 然后设置一个程序的名称。这将会安装 react,并安装与之相关的第三方库,首先会安装一个轻量级的开发服务器 Development Server,然后打包代码的 Webpack,最后是转译器 Babel可以转换我们的代码,还有一些其他的工具。所以在使用工具创建 react 程序的时候,不需要任何配置,所有的设置都配置好的了 ZERO-CONFIG SETUP,但是如果你在生产环境中想自己配置程序,你可以通过 npm run eject 来实现。
1 | create-react-app react-app |
创建好项目之后,我们可以使用以下命令来对我们的项目进行管理。
1 | # Starts the development server. |
首先进入我们创建好的项目的文件夹中,然后在控制台输入 npm start 运行我们的项目。
1 | # 进入 react-app 文件夹 |
项目会在本机开启 3000 端口号运行我们的应用程序,并自动打开默认浏览器,我们的第一个 React 应用程序就建立好了。
我们回到 VS Code 中看到项目的目录结构中,有三个文件夹。
node_modules这个文件夹里面是所有的模块,包括 react 本身。这个文件夹我们不需要关心。public这个文件夹是可公共访问程序的文件夹,里面包含图标、首页、mainfest 文件。index.html文件中的<div id="root"></div>这个标签是我们的 react 程序的容器,所有组件呈现的地方。src这个文件夹是主要的源代码文件夹,里面有个基本的组件也就是 App 组件。我们打开App.js文件可以看到里面使用了一些ES6语法。
ES6: 全称是 ECMAScript6.0,ECMAScript 是一种由 ECMA 国际(European Computer Manufacturers Association)通过 ECMA-262 标准化的脚本程序设计语言,可以理解为是 JavaScript 的一个标准。
我们从零开始,将 src 文件夹下的所有文件删除,然后在 src 文件夹下新建一个 index.js 文件,编写以下代码。
1 | // import 后面是我们要导入的对象名称,from 后面是模块名称 |
当我们保存时这个程序会自动重启,不需要回到浏览器手动刷新,这就是我们常说的 实时模块重载
四、ES6 基本语法
在我们开始真正学习 React 之前,我们先要掌握一些 ES6 的基本知识。
- 变量与常量的关键字(
var、let、const)- 对象(
Objects)- this 关键字(
The this keyword)- 箭头函数(
Arrow Functions)- 对象解构(
Object Destructuring)- 展开操作符(
Spread Operator)- 类(
Classes)- 模块化(
Modules)
如果您对 ES6 的知识已经足够掌握,请跳过第四部分,直接从第五部分开始看。
Ⅰ. let vs var vs const
我们先要说一下创建变量和常量的关键字,我们一般在定义变量时经常会使用 var 关键字,但是 var 关键字有一些小问题。
1 | function sayHello() { |
例如上面的代码在循环的时候定义的变量 i,按理来说 i 应该只是在 for 代码块内部可以被访问的,但是如果我在循环的下边打印 i,就会发现 i 这个变量还是可以被访问的。
1 | function sayHello() { |
这个问题在 ES6 或者说 ECMAScript6,也就是 ECMAScript 2015 当中,我们获得了一个新的变量关键字 let。let 就是来解决这个状况的,当使用 let 来定义变量的时候,这个变量只能在被定义的代码块内部被访问。我们来试验一下。
1 | function sayHello() { |
程序在执行到第 6 行时会报一个 i 未被定义的错误,Line 6: 'i' is not defined no-undef,这个足以证明 let 关键字的作用域是只可能在作用域范围内部才可以使用。
在 ES6 中还有一个关键字 const,我们使用 const 来定义常量,它与 let 类似,const 的作用域也是块,所以 const 定义的常量只能在定义它的块中可以访问。
1 | // 定义常量x并赋值为1 |
在执行这段代码的时候将会报错,Syntax error: "x" is read-only 我们可以看到 x 是只读的,换句话说,如果用 const 定义一个常量,这个常量就不能被再次赋值,它不能改了所以对比变量它叫常量。
注意:
const并不是不可改变的
总结:当以
var来定义变量的时候作用域是函数,当以let来定义变量的时候作用域是代码块,当以const来定义常量的时候作用域也是代码块,所以我们在编写代码的时候可以用let来定义变量,除非有十足的理由使用var,否则我们推荐用const和let取而代之。
Ⅱ. Objects
在 JavaScript 中对象是一组键值对。在 JavaScript 的 OOP(面向对象编程)中,“键”对应的值是函数时我们称之为方法,
在 ES6 的语法中,我们在对象中定义的方法可以不需要写冒号和 function 关键字。
1 | const person = { |
在 ES6 中,有两种可以访问对象成员的方法。
1 | //第一种是 [点操作符] 直接 对象.属性 或 对象.方法 就可以访问对象成员。 |
Ⅲ. this
我们先创建一个对象里面定义一个方法在内部调用 this 关键字,在 js 中 this 的表现行为和 C#或 Java 不太一样,在 js 中,this 总是返回一个当前对象的引用。
1 | const person = { |
this 的取值,取决于函数是如何被调用的,如果你使用的对象的形式是调用方法,this 永远返回这个对象。但是如果使用单独函数的方式调用,this 这个时候指向的是浏览器的全局对象,也就是 window
在 js 中函数也是对象,在 person.walk 的时候实际上是一个对象,我们可以不信的话可以在 person.walk 后面加个点就可以看到它的成员了,里面有个 bind 函数可以用它将函数绑在一个对象上。
1 | const person = { |
Ⅳ. Arrow Functions (箭头函数)
我们先来看看普通函数声明和 ES6 语法的函数声明对比。
1 | // 普通函数声明 |
我们可以看到使用箭头函数声明的函数代码更加的简洁,不需要写 function 关键字,如果函数内只有一行代码连 return 都不需要写。
我们可以再看一个例子,就可以很明显的对比出箭头函数更加的简洁。
1 | const jobs = [ |
我们在箭头函数中定义的 this 和普通函数中的 this 也不太一样。
1 | const person = { |
我们在调用 person 对象的 talk 方法是返回的结果是这个 person 对象,但是如果我们延迟输出这个 this 得到的结果是什么呢?
1 | const person = { |
这里我们在调用 person 对象的 talk 函数时,输出的是 window 对象,而不是 person 对象。原因是这个传入的匿名函数不属于任何对象,它与 person 对象的 talk 方法是不同的,它是一个孤立的函数,我们之前说以一个孤立函数的方式调用函数的时候,默认 this 会返回 window 全局对象,在之前我们没得到 window 而是未定义,是因为之前我们调用的时候严格模式介入了,并将 this 设置为未定义。但是现在这种情况比较特殊,它是回调函数,严格模式并不会重新设定 this 是 window 的行为。
在以前如果我们想解决这种问题,需要这样来写。
1 | const person = { |
我们可以看到这时的 self 指向的时 person 对象了。这是以前的一些写法,现在有了箭头函数就不用这样了。
1 | const person = { |
因为箭头函数不重新绑定 this,使用箭头函数替换匿名回调函数时会继承 this 设置,所以输出的还是 person 对象。
Ⅴ. Object Destructuring 对象解构
在 ES6 语法之前如果我们想要得到一个对象里面的成员会需要这样来写。
1 | const address = { |
声明每个对立的变量对于一个属性,这段代码的问题是我们重复的使用 address. 这样的语法。解构就解决了这样的问题。
1 | const address = { |
这样其实就是把 address 对象对应的属性取出来放在 street 常量中,我们不必把所有的属性全部取出来,如果只想取 street 就把其他的删掉就可以了。
1 | const { street } = address; |
如果我们想要给变量起别名可以使用冒号 : 这样我们就会得到一个新的常量 st。
1 | // 冒号前对应的是 address 的属性,冒号后是常量名 |
ES6 中的
Object Destructuring中文被译为解构或析构
Ⅵ. Spread Operator (展开操作符)
在 ES6 的语法中还有一种常见的写法就是 展开操作符,它的语法是使用 ... 三个点,比如我们想要合并两个数组,可以这样写。
1 | const first = [1, 2, 3]; |
我们可能会疑问了,这不是差不多嘛?好处在哪里?如果我们想在 first 之前或之后添加一个多个元素,就可以看出展开操作符的好处了。
1 | const combined3 = ["a", ...first, "b", ...second, "c"]; |
使用展开操作符也可以轻松组合对象。
1 | const one = { first: "a" }; |
Ⅶ. Classes (类)
类可以帮我们实现代码复用,我们可以通过构造函数 constructor 来传参,这里的 this 永远指向的是 Person 这个类,我们在创建一个类的实例的时候需要使用操作符 new 来实例化它。
1 | class Person { |
在 js 中页面面向对象(OOP),其中继承(inheritance)也是比较重要的一部分,继承之后子类可以调用父类当中的公开成员及函数。
1 | class Person { |
Ⅷ. Modules (模块)
可以看到我们之前写的代码有些乱,因为我们在一个文件中定义了多个类,如果能把代码分割为若干个独立文件就好了,这就是我们说的模块化,将代码写在多个文件里,我们把这些叫做模块,在之前 JavaScript 中没有原生模块的概念,所以就诞生了很多第三方库,但是在 ES6 中已经有原生模块了。我们把这段代码进行模块化。
1 | # person.js 文件 |
1 | # teacher.js 文件 |
1 | # index.js 文件 |
可以看到我们就将之前的代码拆分了到了三个文件里,然后在每个模块中使用 export 和 import 来进行模块的导出和导入的操作,也就实现了我们说的模块化。
我们说在模块里定义的对象默认是私有的,如果不导出,外部是访问不到的,我们一般在模块中导出的不止仅仅只有一个对象,我们需要在每个需要导出的对象前面加入 export 这种方式我们称之为 以名导出(Named Exports) 因为所有要导出的对象都有名字,不同于以名导出还有一种 默认导出(Default of Exports) 的方式,通常只有一个对象需要导出的时候我们就可以使用默认导出。
1 | # teacher.js 文件 |
1 | # index.js 文件 |
你可能会说,类不是对象。其实在 JavaScript 中类就是对象,因为类只是为了完成概念的函数的外皮,函数,我之前说过就是对象,所以在 JavaScript 中类技术上就是对象。
我们可以在一个模块中既使用默认导出又使用以名导出的方式。
1 | # teacher.js 文件 |
1 | # index.js 文件 |
五、第一个 React 组件
我们在终端输入 create-react-app 命令,来创建 react 项目,项目名称叫做 counter-app,当然名称大家可以自定义。
1 | create-react-app counter-app |
工程建立好之后,进入 counter-app 文件夹,启动项目。
1 | # 进入 counter-app 文件夹 |
我们先安装 bootstrap ,这个库是 Twitter 公司开发的 前端开源工具包,这个库是基于 HTML/CSS/JS 开发的响应式布局框架。
1 | npm i bootstrap |
我们回到 src 文件夹中的 index.js 文件中导入 bootstrap 框架。
1 | import React from "react"; |
回到浏览器我们可以看到页面的字体已经改变了。
我们现在要创建组件,为了方便管理我们的组件我们把所有的组件都放在 src 文件夹下新创建的 components 文件夹中,然后在组件文件夹中创建一个新的文件 counter.jsx 命名时我们推荐使用(小)驼峰命名法,即第一个单词的首字母小写,其余单词的首字母大写。创建文件的后缀名我们也推荐使用 .jsx 而不是 .js 为了代码的完整性。
jsx 全称 JavaScript XML,一种在 JavaScript 中构建标签的类似 XML 语法。增强 React 程序组件的可读性
我们在 Visual Studio Code 中安装的
Simple React Snippets扩展,输入一些缩写就可以帮我们快速输入代码。
1 | # counter.jsx |
我们回到 index.js 文件中,导入我们创建的 Counter 组件
1 | // 这个类是默认导出的所以不需要写大括号 |
回到浏览器,就能看到 Counter 组件渲染的结果了。
我们在组件中写 jsx 的语法的时候只能有一个顶级元素,jsx 语法会调用 React.createElement()方法来创建组件,如果有多个顶级元素 babel 不知道如何转译并告知 React 要创建什么元素,所以就会报错。
1 | # counter.jsx |
解决方案就是使用最外层元素用一个 div 标签宝珠它们,保证只有一个顶级元素即可。
1 | # counter.jsx |
如果我们不需要最外层的无意义 div 标签的话可以使用 React.Fragment 组件来替换 div 标签,这样在渲染元素的时候在根组件下面就会只渲染 React.Fragment 组件里面的内容了。
1 | # counter.jsx |
我们可以使用 state 对象来进行组件的动态数据传入,使用大括号可以存放可计算的 js 表达式。
1 | import React, { Component } from "react"; |
比如我们可以添加一个图片,动态的设置 src 属性,我们用 {} 大括号,将需要动态设置的值或属性或方法传入(注意:在为图片设置路径时 大括号 外不需要写 双引号),即可动态生成代码。
1 | import React, { Component } from "react"; |
如果我们需要设置样式,我们可以使用 className 属性 而不是 class 属性,因为这个标签是通过 js 生成的,在 js 中 class 是保留关键字,我们不能去使用它,所以在标签属性中 class 对应的名称就是 className。
1 | import React, { Component } from "react"; |
我们也可以自己设置一些自定义的样式使用 style 关键字,然后通过大括号来进行动态设置。
1 | import React, { Component } from "react"; |
我们还可以设置行内样式,只要在 style 中的大括号里再写一个大括号然后里面设置样式属性即可。
1 | import React, { Component } from "react"; |
我们在开发中很常见的就是列表数据,下面我们来看一下如何渲染列表数据。
1 | import React, { Component } from "react"; |
我们先定义一个数组数据,然后在渲染元素的时候遍历这个数组并返回拼接后的元素,这样写虽然页面上看上去是正常的,但在控制台中可以看到报错,Warning: Each child in a list should have a unique "key" prop. 每个数组的迭代项都要有一个唯一的 key,因为它要区分每个列表项,在 react 的虚拟 DOM 改变的时候 react 可以马上反应过来是什么组件的状态改变了并同步组件,但如果不设置 key 的话 react 是无法知道是哪个组件改变的。我们只需要在渲染 li 的元素时设置 key 属性即可。
1 | <ul> |
我们如果想要实现判断的逻辑,比如渲染列表的时候如果数组中没有数据则返回一条信息 没有任何标签!,否则将渲染列表,因为在 jsx 语法中是没有逻辑判断的因为它不是一个模板引擎,为了渲染我们可以将判断的逻辑放在一个方法中。
1 | import React, { Component } from "react"; |
所有的 react 元素都是基于 dom 元素的属性,例如 按钮元素有一个 onClick 属性用来处理点击事件,同时还有双击事件啊鼠标移入移出事件等。
1 | import React, { Component } from "react"; |
现在我们想实现 点击按钮后增加数字就增加 1,但是在写的时候发现 handleClick 方法中如法调用 this.state.count,这是因为 this 总是返回那个对象引用,但是如果函数被以独立函数的方法调用,this 将会返回 window 对象引用,但如果开启了 严格模式,则会返回 undefined 未定义,这就是为什么我们无法去调用这个属性。
1 | import React, { Component } from "react"; |
我们之前说过可以使用绑定的方式去解决这个问题,我们在 这个类中的 constructor 构造器中绑定 this。
1 | import React, { Component } from "react"; |
其实还有另外一种方式,在 ES6 语法之后不需要使用构造器,只需要将方法改为箭头函数即可,因为箭头函数不会重新绑定 this。
1 | handleClick = () => { |
现在我们要处理事件进行数量的累加,但是我们发现直接更新状态是无效的。
1 | handleClick = () => { |
实际上 state 中的 count 确实已经增加了,但是 react 是不知道的,所以视图并没有被更新,为了解决这个问题,我们需要用到继承自 Component 类的一个方法,setState 方法 告诉 react state 已经更新了,然后让 react 去同步视图,然后浏览器中的 DOM 会根据虚拟 DOM 进行修改,这于 Angular 是不同的,Angular 不需要这样做它也会自动检测并修改,因为 Angular 里所有的浏览器 DOM 都有监听,当点击按钮或输入文字,Angular 会立即意识到修改并更新视图。而在 react 中我们必须告诉它什么东西改变了。
1 | handleClick = () => { |
我们在使用事件时肯定会进行参数的传递,我们可以使用这种箭头函数来处理传参问题。
1 | render() { |
六、React 组合组件
七、分页,过滤,排序
八、
1 |

