React.js

React.js

一、什么是 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
2
3
4
5
6
7
8
# 查看Node.js版本
node -v

# 查看npm版本
npm -v

# 更新npm版本
npm install -g npm

我们先来安装 create-react-app 脚手架工具类库来创建 react 项目。

1
2
3
4
5
#  npm 是我们要执行的包管理工具
# i 代表 install 安装的意思
# -g 代表 global 全局的意思
# create-react-app 是我们要安装的包
npm i -g create-react-app

如果是 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
2
3
4
{
"singleQuoto": true,
"semi": false
}

三、第一个 React 应用

我们使用脚手架创建一个 React 应用程序,我们在控制台中输入 create-react-app 然后设置一个程序的名称。这将会安装 react,并安装与之相关的第三方库,首先会安装一个轻量级的开发服务器 Development Server,然后打包代码的 Webpack,最后是转译器 Babel可以转换我们的代码,还有一些其他的工具。所以在使用工具创建 react 程序的时候,不需要任何配置,所有的设置都配置好的了 ZERO-CONFIG SETUP,但是如果你在生产环境中想自己配置程序,你可以通过 npm run eject 来实现。

1
create-react-app react-app

创建好项目之后,我们可以使用以下命令来对我们的项目进行管理。

1
2
3
4
5
6
7
8
9
10
11
# Starts the development server.
npm start

# Bundles the app into static files for production.
npm run build

# Starts the test runner.
npm test

# Removes this tool and copies build dependencies, configuration files and scripts into the app directionary. If you do this, you can't go back!
npm run eject

首先进入我们创建好的项目的文件夹中,然后在控制台输入 npm start 运行我们的项目。

1
2
3
4
5
# 进入 react-app 文件夹
cd react-app

# 启动项目
npm start

项目会在本机开启 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
2
3
4
5
6
7
8
9
10
11
12
// import 后面是我们要导入的对象名称,from 后面是模块名称
import React from "react";
import ReactDOM from "react-dom";

// 定义一个element常量,并且赋值为一个JSX代码段
const element = <h1>Hello World</h1>;

// 在真实DOM中渲染元素需要调用ReactDOM中的render方法
// 第一个参数是我们传入的想要渲染的元素
// 第二个参数是我们想要在哪里渲染这个元素。在index.html文件中有个root容器标签,我们通过id名root得到这个元素
// 这个ReactDOM方法已经得到了真实DOM元素的引用,然后把element元素渲染到那个引用当中
ReactDOM.render(element, document.getElementById("root"));

当我们保存时这个程序会自动重启,不需要回到浏览器手动刷新,这就是我们常说的 实时模块重载

四、ES6 基本语法

在我们开始真正学习 React 之前,我们先要掌握一些 ES6 的基本知识。

  • 变量与常量的关键字(varletconst)
  • 对象(Objects)
  • this 关键字(The this keyword)
  • 箭头函数(Arrow Functions)
  • 对象解构(Object Destructuring)
  • 展开操作符(Spread Operator)
  • 类(Classes)
  • 模块化(Modules)

如果您对 ES6 的知识已经足够掌握,请跳过第四部分,直接从第五部分开始看。

Ⅰ. let vs var vs const

我们先要说一下创建变量和常量的关键字,我们一般在定义变量时经常会使用 var 关键字,但是 var 关键字有一些小问题。

1
2
3
4
5
6
function sayHello() {
for (var i = 0; i < 5; i++) {
console.log(i);
}
}
sayHello();

例如上面的代码在循环的时候定义的变量 i,按理来说 i 应该只是在 for 代码块内部可以被访问的,但是如果我在循环的下边打印 i,就会发现 i 这个变量还是可以被访问的。

1
2
3
4
5
6
7
8
9
function sayHello() {
for (var i = 0; i < 5; i++) {
console.log(i);
}

// 此处会输出5,因为i在循环到第5次的时候i在最后又进行了+1操作,到第六次循环i已经不再小于5了,所以循环就跳出了
console.log(i);
}
sayHello();

这个问题在 ES6 或者说 ECMAScript6,也就是 ECMAScript 2015 当中,我们获得了一个新的变量关键字 let
let 就是来解决这个状况的,当使用 let 来定义变量的时候,这个变量只能在被定义的代码块内部被访问。我们来试验一下。

1
2
3
4
5
6
7
8
9
function sayHello() {
for (let i = 0; i < 5; i++) {
console.log(i);
}

// 此处会输出5,因为i在循环到第5次的时候i在最后又进行了+1操作,到第六次循环i已经不再小于5了,所以循环就跳出了
console.log(i);
}
sayHello();

程序在执行到第 6 行时会报一个 i 未被定义的错误,Line 6: 'i' is not defined no-undef,这个足以证明 let 关键字的作用域是只可能在作用域范围内部才可以使用。

在 ES6 中还有一个关键字 const,我们使用 const 来定义常量,它与 let 类似,const 的作用域也是块,所以 const 定义的常量只能在定义它的块中可以访问。

1
2
3
4
// 定义常量x并赋值为1
const x = 1;
//为常量x重新赋值为2
x = 2;

在执行这段代码的时候将会报错,Syntax error: "x" is read-only 我们可以看到 x 是只读的,换句话说,如果用 const 定义一个常量,这个常量就不能被再次赋值,它不能改了所以对比变量它叫常量。

注意:const 并不是不可改变的

总结:当以 var 来定义变量的时候作用域是函数,当以 let 来定义变量的时候作用域是代码块,当以 const来定义常量的时候作用域也是代码块,所以我们在编写代码的时候可以用 let 来定义变量,除非有十足的理由使用 var,否则我们推荐用 constlet 取而代之。

Ⅱ. Objects

在 JavaScript 中对象是一组键值对。在 JavaScript 的 OOP(面向对象编程)中,“键”对应的值是函数时我们称之为方法,
在 ES6 的语法中,我们在对象中定义的方法可以不需要写冒号和 function 关键字。

1
2
3
4
5
6
7
8
9
10
11
const person = {
name: "React",
walk: function() {
console.log("walk");
},

// ES6语法,简化了在对象中的方法声明
talk() {
console.log("talk");
}
};

在 ES6 中,有两种可以访问对象成员的方法。

1
2
3
4
5
6
7
//第一种是 [点操作符] 直接 对象.属性 或 对象.方法 就可以访问对象成员。
person.name;
person.walk();

//第二种是使用中括号然后传入字符串去访问属性或方法,这样我们可以重新给成员赋值
person["name"] = "ReactZeroToOne";
person["talk"]();

Ⅲ. this

我们先创建一个对象里面定义一个方法在内部调用 this 关键字,在 js 中 this 的表现行为和 C#或 Java 不太一样,在 js 中,this 总是返回一个当前对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const person = {
name: "React",
walk() {
// 输出 this
console.log(this);
}
};

// 调用talk方法,this会输出 person 对象
person.walk();

//定义常量,赋值为 person 的 walk 成员,这里没有调用walk方法,只是得到了它的引用。
const walk = person.walk;
//得到的是person对象里的walk方法
console.log(walk);

//但是调用walk方法的时候输出的结果却是 undefined
walk();

this 的取值,取决于函数是如何被调用的,如果你使用的对象的形式是调用方法,this 永远返回这个对象。但是如果使用单独函数的方式调用,this 这个时候指向的是浏览器的全局对象,也就是 window

在 js 中函数也是对象,在 person.walk 的时候实际上是一个对象,我们可以不信的话可以在 person.walk 后面加个点就可以看到它的成员了,里面有个 bind 函数可以用它将函数绑在一个对象上。

1
2
3
4
5
6
7
8
9
10
11
12
13
const person = {
name: "React",
walk() {
console.log(this);
}
};
person.walk();

// 将 person.walk 绑定到 person 对象上
// 使用bind方法可以永久的设置this引用
const walk = person.walk.bind(person);
// 这时调用walk函数的时候,就会看到this指向了person对象
walk();

Ⅳ. Arrow Functions (箭头函数)

我们先来看看普通函数声明和 ES6 语法的函数声明对比。

1
2
3
4
5
6
7
// 普通函数声明
const square = function(number) {
return number * number;
};

// 箭头函数声明
const cube = number => number * number * number;

我们可以看到使用箭头函数声明的函数代码更加的简洁,不需要写 function 关键字,如果函数内只有一行代码连 return 都不需要写。

我们可以再看一个例子,就可以很明显的对比出箭头函数更加的简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const jobs = [
{ id: 1, isActived: true },
{ id: 2, isActived: false },
{ id: 3, isActived: true }
];

// 使用数组的filter函数,这个函数会将数据遍历并将每条数据传入这个匿名函数中,这个函数会根据筛选的条件过滤并返回结果
// 这里的筛选条件是只筛选出 isActived 为 true 的数据
const activeJobs = jobs.filter(function(job) {
return job.isActived;
});

// 我们可以使用箭头函数很好的简化这个函数
const activeJobs2 = jobs.filter(job => job.isActived);

我们在箭头函数中定义的 this 和普通函数中的 this 也不太一样。

1
2
3
4
5
6
const person = {
talk() {
console.log("this", this);
}
};
person.talk(); // 输出 person 对象

我们在调用 person 对象的 talk 方法是返回的结果是这个 person 对象,但是如果我们延迟输出这个 this 得到的结果是什么呢?

1
2
3
4
5
6
7
8
const person = {
talk() {
setTimeout(function() {
console.log("this", this);
}, 1000);
}
};
person.talk(); // 输出 window 全局对象

这里我们在调用 person 对象的 talk 函数时,输出的是 window 对象,而不是 person 对象。原因是这个传入的匿名函数不属于任何对象,它与 person 对象的 talk 方法是不同的,它是一个孤立的函数,我们之前说以一个孤立函数的方式调用函数的时候,默认 this 会返回 window 全局对象,在之前我们没得到 window 而是未定义,是因为之前我们调用的时候严格模式介入了,并将 this 设置为未定义。但是现在这种情况比较特殊,它是回调函数,严格模式并不会重新设定 this 是 window 的行为。

在以前如果我们想解决这种问题,需要这样来写。

1
2
3
4
5
6
7
8
9
10
11
const person = {
talk() {
// 我们声明一个变量来保存此时的this
var self = this;
setTimeout(function() {
// 在回调函数中调用self
console.log("self", self);
}, 1000);
}
};
person.talk(); // 输出 person 对象

我们可以看到这时的 self 指向的时 person 对象了。这是以前的一些写法,现在有了箭头函数就不用这样了。

1
2
3
4
5
6
7
8
9
const person = {
talk() {
// 将匿名函数修改为箭头函数
setTimeout(() => {
console.log("this", this);
}, 1000);
}
};
person.talk(); //输出 person 对象

因为箭头函数不重新绑定 this,使用箭头函数替换匿名回调函数时会继承 this 设置,所以输出的还是 person 对象。

Ⅴ. Object Destructuring 对象解构

在 ES6 语法之前如果我们想要得到一个对象里面的成员会需要这样来写。

1
2
3
4
5
6
7
8
const address = {
street: "",
city: "",
country: ""
};
const street = address.street;
const city = address.city;
const country = address.country;

声明每个对立的变量对于一个属性,这段代码的问题是我们重复的使用 address. 这样的语法。解构就解决了这样的问题。

1
2
3
4
5
6
7
const address = {
street: "",
city: "",
country: ""
};

const { street, city, country } = address;

这样其实就是把 address 对象对应的属性取出来放在 street 常量中,我们不必把所有的属性全部取出来,如果只想取 street 就把其他的删掉就可以了。

1
const { street } = address;

如果我们想要给变量起别名可以使用冒号 : 这样我们就会得到一个新的常量 st

1
2
// 冒号前对应的是 address 的属性,冒号后是常量名
const { street: st } = address;

ES6 中的 Object Destructuring 中文被译为 解构析构

Ⅵ. Spread Operator (展开操作符)

在 ES6 的语法中还有一种常见的写法就是 展开操作符,它的语法是使用 ... 三个点,比如我们想要合并两个数组,可以这样写。

1
2
3
4
5
6
7
8
const first = [1, 2, 3];
const second = [4, 5, 6];

// 使用数组的 concat 函数拼接两个数组
const combined = first.concat(second);

// 使用展开操作符来拼接数组
const combined2 = [...first, ...second];

我们可能会疑问了,这不是差不多嘛?好处在哪里?如果我们想在 first 之前或之后添加一个多个元素,就可以看出展开操作符的好处了。

1
const combined3 = ["a", ...first, "b", ...second, "c"];

使用展开操作符也可以轻松组合对象。

1
2
3
4
5
6
const one = { first: "a" };
const two = { second: "b" };

// 组合one、two、和其他属性
// 结果是:{ first:"a", second:"b", third:"c" }
const combined = { ...one, ...two, third: "c" };

Ⅶ. Classes (类)

类可以帮我们实现代码复用,我们可以通过构造函数 constructor 来传参,这里的 this 永远指向的是 Person 这个类,我们在创建一个类的实例的时候需要使用操作符 new 来实例化它。

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name) {
this.name = name;
}
walk() {
console.log("walk");
}
}

// 实例化 Person 类,传入参数 name
const person = new Person("React");

在 js 中页面面向对象(OOP),其中继承(inheritance)也是比较重要的一部分,继承之后子类可以调用父类当中的公开成员及函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
constructor(name) {
this.name = name;
}
walk() {
console.log("walk");
}
}

// 使用 extends 关键字继承 Person 类
class Teacher extends Person {
constructor(name, degree) {
// super 关键字调用父类的构造器,传入参数name,如果不写super就会报错
super(name);

this.degree = degree;
}
teach() {
console.log("teach");
}
}

const teacher = new Teacher("React", "MSC");

Ⅷ. Modules (模块)

可以看到我们之前写的代码有些乱,因为我们在一个文件中定义了多个类,如果能把代码分割为若干个独立文件就好了,这就是我们说的模块化,将代码写在多个文件里,我们把这些叫做模块,在之前 JavaScript 中没有原生模块的概念,所以就诞生了很多第三方库,但是在 ES6 中已经有原生模块了。我们把这段代码进行模块化。

1
2
3
4
5
6
7
8
9
10
11
12
# person.js 文件

// 模块默认是私有的,也就是说这个类除了这个模块外都是不可见的,为了让其他的模块也可以使用,我们可以把它公开化
// 实现公开化的方法就是导出,在class关键字前加入 export 关键字就可实现导出,然后就可以在需要的地方导入就可以了
expxort class Person {
constructor(name) {
this.name = name;
}
walk() {
console.log("walk");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# teacher.js 文件

// 我们可以使用 import from 将想要导入的模块加载进来
import { Person } from './person';

export class Teacher extends Person {
constructor(name, degree) {
super(name);

this.degree = degree;
}
teach() {
console.log("teach");
}
}
1
2
3
4
5
6
# index.js 文件

import { Teacher } from './teacher';

const teacher = new Teacher("React", "MSC");
teacher.teach();

可以看到我们就将之前的代码拆分了到了三个文件里,然后在每个模块中使用 exportimport 来进行模块的导出和导入的操作,也就实现了我们说的模块化。

我们说在模块里定义的对象默认是私有的,如果不导出,外部是访问不到的,我们一般在模块中导出的不止仅仅只有一个对象,我们需要在每个需要导出的对象前面加入 export 这种方式我们称之为 以名导出(Named Exports) 因为所有要导出的对象都有名字,不同于以名导出还有一种 默认导出(Default of Exports) 的方式,通常只有一个对象需要导出的时候我们就可以使用默认导出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# teacher.js 文件

import { Person } from './person';

// 在 export 关键字的后面加入 default 关键字 声明这个类就是这个模块的默认导出对象
export default class Teacher extends Person {
constructor(name, degree) {
super(name);

this.degree = degree;
}
teach() {
console.log("teach");
}
}
1
2
3
4
5
6
7
# index.js 文件

// 在引入 Teacher 类的时候,因为这个类是默认对象所以不需要写大括号
import Teacher from './teacher';

const teacher = new Teacher("React", "MSC");
teacher.teach();

你可能会说,类不是对象。其实在 JavaScript 中类就是对象,因为类只是为了完成概念的函数的外皮,函数,我之前说过就是对象,所以在 JavaScript 中类技术上就是对象。

我们可以在一个模块中既使用默认导出又使用以名导出的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# teacher.js 文件

import { Person } from './person';

// 以名导出
export function promote(){}

// 默认导出
export default class Teacher extends Person {
constructor(name, degree) {
super(name);

this.degree = degree;
}
teach() {
console.log("teach");
}
}
1
2
3
4
5
6
7
# index.js 文件

// 在引入的时候,不写括号来接收默认导出的对象,使用大括号接收以名导出的对象
import Teacher, { promote } from './teacher';

const teacher = new Teacher("React", "MSC");
teacher.teach();

五、第一个 React 组件

我们在终端输入 create-react-app 命令,来创建 react 项目,项目名称叫做 counter-app,当然名称大家可以自定义。

1
create-react-app counter-app

工程建立好之后,进入 counter-app 文件夹,启动项目。

1
2
3
4
5
# 进入 counter-app 文件夹
cd .\counter-app\

# 启动项目
npm start

我们先安装 bootstrap ,这个库是 Twitter 公司开发的 前端开源工具包,这个库是基于 HTML/CSS/JS 开发的响应式布局框架。

1
npm i bootstrap

我们回到 src 文件夹中的 index.js 文件中导入 bootstrap 框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

// 引入bootstrap样式
import "bootstrap/dist/css/bootstrap.min.css";

ReactDOM.render(<App />, document.getElementById("root"));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

回到浏览器我们可以看到页面的字体已经改变了。

我们现在要创建组件,为了方便管理我们的组件我们把所有的组件都放在 src 文件夹下新创建的 components 文件夹中,然后在组件文件夹中创建一个新的文件 counter.jsx 命名时我们推荐使用(小)驼峰命名法,即第一个单词的首字母小写,其余单词的首字母大写。创建文件的后缀名我们也推荐使用 .jsx 而不是 .js 为了代码的完整性。

jsx 全称 JavaScript XML,一种在 JavaScript 中构建标签的类似 XML 语法。增强 React 程序组件的可读性

我们在 Visual Studio Code 中安装的 Simple React Snippets 扩展,输入一些缩写就可以帮我们快速输入代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# counter.jsx

import React, { Component } from "react";

// Counter 类 继承自 React的 Component 类
class Counter extends Component {
render() {
// jsx 语法
// 它会让编译器调用 React.createElement 的方法
return <h1>Hello World</h1>;
}
}

// 我们也可以这样来实现默认导出类
export default Counter;

我们回到 index.js 文件中,导入我们创建的 Counter 组件

1
2
3
4
5
6
// 这个类是默认导出的所以不需要写大括号
import Counter from "./components/counter";

// 将原来使用的 <App /> 标签修改为 我们创建的 <Counter /> 组件
//ReactDOM.render(<App />, document.getElementById("root"));
ReactDOM.render(<Counter />, document.getElementById("root"));

回到浏览器,就能看到 Counter 组件渲染的结果了。

我们在组件中写 jsx 的语法的时候只能有一个顶级元素,jsx 语法会调用 React.createElement()方法来创建组件,如果有多个顶级元素 babel 不知道如何转译并告知 React 要创建什么元素,所以就会报错。

1
2
3
4
5
6
7
8
9
10
11
12
# counter.jsx

import React, { Component } from "react";

class Counter extends Component {
render() {
// jsx语法报错,只能有一个顶级元素
return <h1>Hello</h1><h1>World</h1>;
}
}

export default Counter;

解决方案就是使用最外层元素用一个 div 标签宝珠它们,保证只有一个顶级元素即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# counter.jsx

import React, { Component } from "react";

class Counter extends Component {
render() {
// 将所有的元素用div标签包含进来
// 当有多行标签的时候,需要在外边用小括号括起来
return (
<div>
<h1>Hello</h1>
<h1>World</h1>
</div>
);
}
}

export default Counter;

如果我们不需要最外层的无意义 div 标签的话可以使用 React.Fragment 组件来替换 div 标签,这样在渲染元素的时候在根组件下面就会只渲染 React.Fragment 组件里面的内容了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# counter.jsx

import React, { Component } from "react";

class Counter extends Component {
render() {
return (
<React.Fragment>
<h1>Hello</h1>
<h1>World</h1>
</React.Fragment>
);
}
}

export default Counter;

我们可以使用 state 对象来进行组件的动态数据传入,使用大括号可以存放可计算的 js 表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from "react";
class Counter extends Component {
// state是react组件中一个特殊的属性,实际它包含了这个组件需要用到的数据
state = {
count: 0
};
render() {
return (
<div>
{/* this 指向当前类对象,在大括号也可以调用方法 */}
<span>{formatCount()}</span>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

比如我们可以添加一个图片,动态的设置 src 属性,我们用 {} 大括号,将需要动态设置的值或属性或方法传入(注意:在为图片设置路径时 大括号 外不需要写 双引号),即可动态生成代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from "react";
class Counter extends Component {
state = {
imageUrl: "https://picsum.photos/600"
};
render() {
return (
<div>
<img src={this.state.imageUrl} alt="" />
</div>
);
}
}

export default Counter;

如果我们需要设置样式,我们可以使用 className 属性 而不是 class 属性,因为这个标签是通过 js 生成的,在 js 中 class 是保留关键字,我们不能去使用它,所以在标签属性中 class 对应的名称就是 className

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};
render() {
return (
<div>
{/* 为 span 元素设置 bootstrap 样式 */}
<span className="badge badge-primary m-2">{this.formatCount()}</span>
{/* 添加button按钮并设置样式 */}
<button className="btn btn-primary btn-sm">Increment</button>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

我们也可以自己设置一些自定义的样式使用 style 关键字,然后通过大括号来进行动态设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};

// 定义自定义样式
styles = {
fontSize: "50px",
fontWeight: "bolder"
};

render() {
return (
<div>
{/* 使用 style 动态设置样式 */}
<span style={this.styles} className="badge badge-primary m-2">
{this.formatCount()}
</span>
<button className="btn btn-primary btn-sm">Increment</button>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

我们还可以设置行内样式,只要在 style 中的大括号里再写一个大括号然后里面设置样式属性即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};

render() {
return (
<div>
{/* 在style的大括号中再写一个大括号然后里面写样式属性即可 */}
<span style={{ fontSize: 30 }} className="badge badge-primary m-2">
{this.formatCount()}
</span>
<button className="btn btn-primary btn-sm">Increment</button>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

我们在开发中很常见的就是列表数据,下面我们来看一下如何渲染列表数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from "react";
class Counter extends Component {
state = {
// 定义渲染的列表数据
tags: ["Tag1", "Tag2", "Tag3", "Tag4"]
};

render() {
return (
<React.Fragment>
<ul>
{/* 我们使用数组的 map 方法遍历数组,渲染元素并设置值 */}
{this.state.tags.map(tag => (
<li>{tag}</li>
))}
</ul>
</React.Fragment>
);
}
}

export default Counter;

我们先定义一个数组数据,然后在渲染元素的时候遍历这个数组并返回拼接后的元素,这样写虽然页面上看上去是正常的,但在控制台中可以看到报错,Warning: Each child in a list should have a unique "key" prop. 每个数组的迭代项都要有一个唯一的 key,因为它要区分每个列表项,在 react 的虚拟 DOM 改变的时候 react 可以马上反应过来是什么组件的状态改变了并同步组件,但如果不设置 key 的话 react 是无法知道是哪个组件改变的。我们只需要在渲染 li 的元素时设置 key 属性即可。

1
2
3
4
5
6
7
<ul>
{this.state.tags.map(tag => (
{/* 设置key属性,控制台中将不会再报错 */}
{/* 注意:这里的key在列表中必须是唯一的,但不必在整个程序中唯一,只在当前列表中唯一即可 */}
<li key={tag}>{tag}</li>
))}
</ul>

我们如果想要实现判断的逻辑,比如渲染列表的时候如果数组中没有数据则返回一条信息 没有任何标签!,否则将渲染列表,因为在 jsx 语法中是没有逻辑判断的因为它不是一个模板引擎,为了渲染我们可以将判断的逻辑放在一个方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, { Component } from "react";
class Counter extends Component {
state = {
// 空的列表数据
tags: [] //[("Tag1", "Tag2", "Tag3", "Tag4")]
};

renderTags() {
// 如果标签没有数据则返回信息
if (this.state.tags.length === 0) return <p>没有任何标签!</p>;

// 否则渲染列表
return (
<ul>
{this.state.tags.map(tag => (
<li key={tag}>{tag}</li>
))}
</ul>
);
}

render() {
return (
<React.Fragment>
{/* 我们可以使用这种方式判断数组中如果没有数据,则返回 请先输入标签! 信息 */}
{this.state.tags.length === 0 && "请先创建标签!"}
{/* 调用渲染标签方法 */}
{this.renderTags()}
</React.Fragment>
);
}
}

export default Counter;

所有的 react 元素都是基于 dom 元素的属性,例如 按钮元素有一个 onClick 属性用来处理点击事件,同时还有双击事件啊鼠标移入移出事件等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from "react";
class Counter extends Component {
state = {};

// 处理点击事件
handleClick() {
console.log("Increment 按钮被点击了。");
}

render() {
return (
<React.Fragment>
{/* 注意这里的调用的函数并没有使用括号 this.handleClick(),
如果使用括号会直接调用这个函数,我们只放置这个函数的引用即可 */}
<button onClick={this.handleClick} className="btn btn-primary">
Increment
</button>
</React.Fragment>
);
}
}

export default Counter;

现在我们想实现 点击按钮后增加数字就增加 1,但是在写的时候发现 handleClick 方法中如法调用 this.state.count,这是因为 this 总是返回那个对象引用,但是如果函数被以独立函数的方法调用,this 将会返回 window 对象引用,但如果开启了 严格模式,则会返回 undefined 未定义,这就是为什么我们无法去调用这个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};

handleClick() {
// 调用 this.state.count 将会报错
console.log("Increment 按钮被点击了。" + this.state.count);
}

render() {
return (
<div>
<span className="badge badge-primary m-2">{this.formatCount()}</span>
<button onClick={this.handleClick} className="btn btn-primary btn-sm">
Increment
</button>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

我们之前说过可以使用绑定的方式去解决这个问题,我们在 这个类中的 constructor 构造器中绑定 this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React, { Component } from "react";
class Counter extends Component {
state = {
count: 0
};

constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
console.log("Increment 按钮被点击了。" + this.state.count);
}

render() {
return (
<div>
<span className="badge badge-primary m-2">{this.formatCount()}</span>
<button onClick={this.handleClick} className="btn btn-primary btn-sm">
Increment
</button>
</div>
);
}

formatCount() {
const { count } = this.state;
return count === 0 ? <h1>Zero</h1> : count;
}
}

export default Counter;

其实还有另外一种方式,在 ES6 语法之后不需要使用构造器,只需要将方法改为箭头函数即可,因为箭头函数不会重新绑定 this。

1
2
3
handleClick = () => {
console.log("Increment 按钮被点击了。" + this.state.count);
};

现在我们要处理事件进行数量的累加,但是我们发现直接更新状态是无效的。

1
2
3
handleClick = () => {
this.state.count++;
};

实际上 state 中的 count 确实已经增加了,但是 react 是不知道的,所以视图并没有被更新,为了解决这个问题,我们需要用到继承自 Component 类的一个方法,setState 方法 告诉 react state 已经更新了,然后让 react 去同步视图,然后浏览器中的 DOM 会根据虚拟 DOM 进行修改,这于 Angular 是不同的,Angular 不需要这样做它也会自动检测并修改,因为 Angular 里所有的浏览器 DOM 都有监听,当点击按钮或输入文字,Angular 会立即意识到修改并更新视图。而在 react 中我们必须告诉它什么东西改变了。

1
2
3
4
5
handleClick = () => {
this.setState({
count: (this.state.count += 1)
});
};

我们在使用事件时肯定会进行参数的传递,我们可以使用这种箭头函数来处理传参问题。

1
2
3
4
5
6
7
8
9
10
render() {
return (
<div>
<span className="badge badge-primary m-2">{this.formatCount()}</span>
<button onClick={()=> this.handleClick(params) } className="btn btn-primary btn-sm">
Increment
</button>
</div>
);
}

六、React 组合组件

七、分页,过滤,排序

八、

1
2


评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×