webpack的模块化实现

源文件:a.js, b.js, c.js, d.js, index.js

展开源码

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
// a.js
const a = {
x: 1
}
module.exports = a

// b.js
const b = {
x: 1
}
export default b

// c.js
export default {
x: 1
}

// d.js
const d = {
x: 1
}
export { d }

// index.js
import b from './b'
import c from './c'
import { d } from './d'
const a = require('./a')

console.log('a:', a)
console.log('b:', b)
console.log('c:', c)
console.log('d:', d)

就是在 index.js  中引入 [a, b, c, d]  等使用不同模块化方式的模块, 来看看 webpack 是怎么处理模块引用的

下面是 webpack 打包出来的 bundle.js 文件(省略了多余注释和没用到的声明)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
(function (modules) {
// 代码模块缓存, 将执行过的代码模块缓存起来
var installedModules = {};

// 实现模块化代码的主要函数, 通过以文件名为模块id(moduleId)递归调用
function __webpack_require__(moduleId) {
// 若模块已缓存, 则直接调用
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建新的模块代码, 并把模块缓存
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {},
});

// 执行对应模块id的代码, 并将模块对象和方法绑定到当前module上
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// 将模块已加载标识置为true
module.l = true;

// 返回模块对外暴露方法
return module.exports;
}

// 模块化处理函数: 用于 export { aaa, bbb } , import { aaa } from './*
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};

// 挂载 _esModule 属性到 exports 上
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};

// 封装了 Object.prototype.hasOwnProperty.call
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};

// 代码启动, 传入入口文件的模块id
return __webpack_require__((__webpack_require__.s = './src/index.js'));
})({
'./src/a.js': function (module, exports) {
/**
* commonjs 模块
* 直接将导出的对象赋值为 module.exports
*/
eval('const a = {\n x: 1\n}\n\nmodule.exports = a\n\n//# sourceURL=webpack:///./src/a.js?');
},

'./src/b.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 默认导出模块 export default
* 会在 module.exports 属性挂载属性 _esModule = true
* 然后将导出对象挂载到 module.exports.default 上
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\nconst b = {\n x: 1\n}\n\n/* harmony default export */ __webpack_exports__["default"] = (b);\n\n//# sourceURL=webpack:///./src/b.js?'
);
},

'./src/c.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 模块
* 同 模块b
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__["default"] = ({\n x: 1\n});\n\n//# sourceURL=webpack:///./src/c.js?'
);
},

'./src/d.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* es6 命名导出模块 export const d = {}
* 会在 module.exports 属性挂载属性 _esModule = true
* 通过 getter 在访问 module.exports[name] 时返回导出对象
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return d; });\nconst d = {\n x: 1\n}\n\n\n\n//# sourceURL=webpack:///./src/d.js?'
);
},

'./src/index.js': function (module, __webpack_exports__, __webpack_require__) {
/**
* 入口文件
* 通过 _webpack_require__() 得到导入对象,即 编译后的 module.exports 属性
* 对不同的模块化方式做不同的引入处理
*/
'use strict';
eval(
'__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ "./src/b.js");\n/* harmony import */ var _c__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./c */ "./src/c.js");\n/* harmony import */ var _d__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./d */ "./src/d.js");\n\n\n\nconst a = __webpack_require__(/*! ./a */ "./src/a.js")\n\nconsole.log(\'a\', a)\nconsole.log(\'b\', _b__WEBPACK_IMPORTED_MODULE_0__["default"], _b__WEBPACK_IMPORTED_MODULE_0__)\nconsole.log(\'c\', _c__WEBPACK_IMPORTED_MODULE_1__["default"], _c__WEBPACK_IMPORTED_MODULE_1__)\nconsole.log(\'d\', _d__WEBPACK_IMPORTED_MODULE_2__["d"], _d__WEBPACK_IMPORTED_MODULE_2__)\n\n//# sourceURL=webpack:///./src/index.js?'
);
},
});

整体代码流程就是创建一个自执行函数, 根据文件名称递归调用内部函数 __webpack_require__(moduleId) , 从而把代码模块化

node常用模块

fs(文件系统) 模块

fs 模块提供了一个 API,用于以模仿标准 POSIX 函数的方式与文件系统进行交互。

所有文件系统操作都具有同步和异步的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
读取文件;
const fs = require('fs');

// 异步读取
fs.readFile('./index.txt', 'utf8', (err, data) => {
console.log(data); // Hello Nodejs
});

// 同步读取
const data = fs.readFileSync('./index.txt', 'utf8');

console.log(data); // Hello Nodejs

// 创建读取流
const stream = fs.createReadStream('./index.txt', 'utf8');

// 这里fs.createReadStream用到了前面介绍的events eventEmitter.on() 方法来监听事件
stream.on('data', data => {
console.log(data); // Hello Nodejs
});

写入/修改文件
写入文件时,如果文件不存在,则会创建并写入,如果文件存在,会覆盖文件内容.

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs');
// 异步写入
fs.writeFile('./write.txt', 'Hello Nodejs', 'utf8', err => {
if (err) throw err;
});
// 同步写入
fs.writeFileSync('./writeSync.txt', 'Hello Nodejs');
// 文件流写入
const ws = fs.createWriteStream('./writeStream.txt', 'utf8');
ws.write('Hello Nodejs');
ws.end();

删除文件/文件夹
删除文件

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 异步删除文件
fs.unlink('./delete.txt', err => {
if (err) throw err;
});

// 同步删除文件
fs.unlinkSync('./deleteSync.txt');
删除文件夹;
// 异步删除文件夹
fs.rmdir('./rmdir', err => {
if (err) throw err;
});

// 同步删除文件夹
fs.rmdirSync('./rmdirSync');
创建文件夹;
// 异步创建文件夹
fs.mkdir('./mkdir', err => {
if (err) throw err;
});

// 同步创建文件夹
fs.mkdirSync('./mkdirSync');
重命名文件 / 文件夹;
const fs = require('fs');

// 异步重命名文件
fs.rename('./rename.txt', './rename-r.txt', err => {
if (err) throw err;
});

// 同步重命名文件夹
fs.renameSync('./renameSync', './renameSync-r');
复制文件 / 文件夹;
const fs = require('fs');

// 异步复制文件
fs.copyFile('./copy.txt', './copy-c.txt', (err, copyFiles) => {
if (err) throw err;
});

// 同步复制文件夹
fs.copyFileSync('./null', 'null-c');
文件夹状态 - 文件 / 文件夹;
const fs = require('fs');

// 异步获取文件状态
fs.stat('./dir', (err, stats) => {
if (err) throw err;
// 是否是文件类型
console.log(stats.isFile()); // false
// 是否是文件夹类型
console.log(stats.isDirectory()); // true
});

// 同步获取文件状态
const stats = fs.statSync('./stats.txt');

// 是否是文件类型
console.log(stats.isFile()); // true
// 是否是文件夹类型
console.log(stats.isDirectory()); // false

events 模块

Node 是基于事件驱动的,其就是通过核心模块 Events 实现的。 Events 模块定义了 EventEmitter 类。实际上就是发布订阅模式。所有可能触发事件的对象都是继承自 EventEmitter 类的,其主要包含下面方法:

  • addEventListener
  • on
  • once
  • removeListener
  • removeAllListeners
  • setMaxListeners
  • emit

使用 EventEmitter
其他类需要触发事件等,都需要自行继承这个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
let util = require('util')
let EventEmitter = require('events')

function OtherClass(path) {
EventEmitter.call(this)
}
util.inherits(OtherClass, EventEmitter)
newListener 事件
EventEmitter 实例还可以监听一个 newListener 事件,即每次给这个实例添加新的监听函数时,都会触发 newListener 事件。

this.on('newListener', function (type, listener) {
// 每当监听一个事件时,就执行某些行为
})

stream 模块

流(stream)是 Node.js 中处理流式数据的抽象接口。 stream 模块用于构建实现了流接口的对象。

使用 stream 的场景:

使用 fs.readFile 读取文件时会将数据整个读取到内存中再处理,当处理大文件时,会极大的展示资源,这时就是使用 stream 的时候了。

Stream 不会一次性的读取文件,而是分批次的读取适量的内容到缓存区进行操作,这样对内存的占用会少很多。

流的四种基本类型

Writable - 可写入数据的流(例如 fs.createWriteStream())。

Readable - 可读取数据的流(例如 fs.createReadStream())。

Duplex - 可读又可写的流(例如 net.Socket)。

Transform - 在读写过程中可以修改或转换数据的 Duplex 流(例如 zlib.createDeflate())。

缓冲

缓冲区有大小限制,由 highWaterMark 指定字节总数(对象模式下指定对象总数)。调用 stream.push(chunk)时,数据缓冲在可读流中,等待被消费,如果缓冲区的数据大小达到了 highWaterMark 指定的阈值,流会停止读取数据,直到当前缓冲区的数据被消费。

用于消费的 api

使用继承自 events 的 emitter api 来通讯状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const file = fs.createReadStream('./test.txt', {
highWaterMark: 2,
flags: 'r',
encoding: 'utf8',
});

file.on('open', a => {
console.log('open', a);
});

file.on('data', chunk => {
console.log('data', chunk);
});

file.on('end', chunk => {
console.log('end', chunk);
});

file.on('close', a => {
console.log('close', a);
});
// open -> data -> data -> ... -> end -> close

(异步)事件触发顺序 open -> data -> data -> … -> end -> close

URL 模块

url 模块用于处理与解析 URL。

URL 字符串是结构化的字符串,包含多个含义不同的组成部分。 解析字符串后返回的 URL 对象,每个属性对应字符串的各个组成部分。

url 模块提供两种 api 来处理 url。(两种 api 的处理结果不同,详细)

const url = require(‘url’)
// WHATWG 标准
const myUrl = new url(‘https://baidu.com:8080/a/b/c?q=str#hash')
// 遗留 api
const myUrl2 = url.parse(‘https://baidu.com:8080/a/b/c?q=str#hash')
以下介绍的都是 WHATWG 标准解析出的对象

URL 对象的属性
const url = require(“url”);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const url = require("url");

const myURL = new url("https://github.com/webfansplz#hello");
console.log(myURL);
{
href: 'https://github.com/webfansplz#hello', // 序列化的 URL
origin: 'https://github.com', // 序列化的 URL 的 origin
protocol: 'https:', // URL 的协议
username: '', // URL 的用户名
password: '', // URL 的密码
host: 'github.com', // URL 的主机
hostname: 'github.com', // URL 的主机名
port: '', // URL 的端口
pathname: '/webfansplz', // URL 的路径
search: '', // URL 的序列化查询参数
searchParams: URLSearchParams {}, // URL 查询参数的 URLSearchParams 对象
hash: '#hello' // URL 的片段
}

URL 对象属性 除了 origin 和 searchParams 是只读的,其他都是可写的.

  1. 序列化 URL
1
2
3
4
5
6
7
8
9
const { URL } = require('url');

const myURL = new URL('https://github.com/webfansplz#hello');

console.log(myURL.href); // https://github.com/webfansplz#hello

console.log(myURL.toString()); // https://github.com/webfansplz#hello

console.log(myURL.toJSON()); // https://github.com/webfansplz#hello

初识MongoDB

数据库分类

关系型

SQL Server MySQL Access ORACLE

数据库 –> 表 –> 记录

非关系型

MongoDb 文档型的非关系数据库

安装

官网下载合适版本
不停的下一步,下一步
增加环境变量,以便直接在 shell 中使用
数据库 –> 集合 –> 文档 database –> collection –> document

存储格式 BSON 类似于 JSON

每一个 {}称为一条文档

{ ​ “name” : “小明”, ​ “age” : 18, ​ “hobby”: [“睡觉”, “吃饭”] }

命令操作

创建数据库

mongod:
mongod --dbpath dir # 打开或新建一个数据库 # mongod --dbpath E:\mongodb\mydb
mongo:
use dbname # 新建叫做 dbname 的数据库, 同时进入 dbname
初步操作

1
2
3
4
5
6
7
show dbs # 查看所有的数据库
show collections # 查看当前库下所有的 集合

## 在叫做 collectionName 的集合中插入一条文档,如果集合不存在,则新建该集合
db.collectionName.insert(obj)
## 查找名为 collectionName 集合的所有文档
db.collectionName.find()

导入数据

假设 C:\user\db\test.json 是 1000 条文档

1
2
3
4
5
6
7
8
mongoimport --db test --collection user --drop --file C:\user\db\test.json

## 以上操作会把 test.json 里的文档全部导入到 数据库 test -> 集合 user 中

--db 导入到哪个库
--collection 导入到哪个集合
--drop 清空集合中原有文档
--file 要导入的文件路径

CRUD

假设集合名叫 user

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
## 查询所有文档
db.user.find()

# 查询 k 的值为 v 的所有文档
db.user.find({k: v})

# and 操作
# 查询 k1 的值为 v1 且 k2 的值为 v2 的所有文档
db.user.find({k1: v1, k2: v2})

# or 操作
# 查询 k1 的值为 v1 或 k2 的值为 v2 的所有文档
db.user.find({$or: [{k1: v1}, {k2: v2}]})

# 比较(大于,小于)
db.user.find({k: {$gt: v}}) # 查询 k 的值大于 v 的文档
db.user.find({k: {$lt: v}}) # 查询 k 的值小于 v 的文档
# $gt 大于
# $lt 小于

# 组合
db.user.find({k: {$gt: v1, $lt: v2}}) # 查询 k 的值大于 v1 同时小于 v2 的文档
# 更新
db.user.update(
{k: v1}, # 查询条件
{
$set: {k: v2} # 把查到的 k 改为 v2
}
)
# 更新详细语法
db.user.update(
<query>, # 查询条件
<update>, # 更新方式,如 $set, $inc 等
{
multi: <boolean>, # (可选)默认为 false,只匹配找到的第一条, 如果为 true, 所有满足条件的都匹配
}
)
# 删除
db.dropDatabase() # 删除当前所在数据库
db.user.drop() # 删除名为 user 的集合
db.user.remove({k: v}) # 删除匹配到的所有 k 值为 v 的文档
db.user.remove({k: v}, {justOne: true}) # 删除第一个匹配到的所有 k 值为 v 的文档
db.user.remove({}) # 清空 user 集合
排序
db.user.find({查询条件}).sort({k1: 1}, {k2: -1})

# 按照 k1 来排序, 如果 k1 的值相同,按照 k2 来排序

# 1 升序, -1 降序

node 里简单封装增删改查

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// node mongodb 版本 v3.1.13 适用
const MongoClient = require('mongodb').MongoClient;
const log = console.log.bind(console);

class Dao {
/**
* 构造函数
* @param {string} url
* @param {string} dbname
* @param {string} colname
*/
constructor(url, dbname, colname) {
this.url = url;
this.dbname = dbname;
this.colname = colname;
}

/**
* 连接数据库
*/
_connect() {
return new Promise((resolve, reject) => {
MongoClient.connect(this.url, { useNewUrlParser: true }, (err, client) => {
if (err) {
reject(err);
} else {
resolve(client);
}
});
});
}

/**
* 插入文档
* @param {arr || object} documents
* @param {boolean} insertMany
* 使用 insertMany(arr), 在 arr === [] 时,会报错
*/
insert(documents, insertMany = false) {
return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
if (insertMany) {
col
.insertMany(documents)
.then(res => {
resolve(res);
})
.catch(err => {
log(err);
});
client.close();
} else {
col
.insertOne(documents)
.then(res => {
resolve(res);
})
.catch(err => {
log(err);
});
client.close();
}
})
.catch(err => {
reject(err);
});
});
}

/**
* 查询
* @param {object} document
*/
query(document, pageConfig) {
document = document || {};
pageConfig = pageConfig || {};
let page = pageConfig.page;
let amount = pageConfig.amount;
const resData = [];

return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
let cursor = col
.find(document)
.limit(amount)
.skip((page - 1) * amount);
cursor.each((err, data) => {
if (err) {
reject(err);
client.close();
} else if (data !== null) {
resData.push(data);
} else {
resolve(resData);
client.close();
}
});
})
.catch(err => {
log(err);
});
});
}

/**
* 删除集合中的数据
* @param {object} query
* @param {boolean} deleteMany
*/
delete(query, deleteMany = false) {
return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
if (deleteMany) {
col.deleteMany(query).then(res => {
resolve(res);
client.close();
});
} else {
col.deleteOne(query).then(res => {
resolve(res);
client.close();
});
}
})
.catch(err => {
log(err);
});
});
}

/**
* 更新
* @param {obj} filter
* @param {obj} updater
*/
update(filter, updater) {
let uptaterCpy = { $set: updater };

return new Promise((resolve, reject) => {
this._connect()
.then(client => {
let col = client.db(this.dbname).collection(this.colname);
col.updateMany(filter, uptaterCpy).then(res => {
resolve(res);
client.close();
});
})
.catch(err => {
log(err);
});
});
}
}

//
let url = 'mongodb://localhost:27017';
let dbname = 'test';
let colname = 'user';
let dao = new Dao(url, dbname, colname);

let arr = [];
for (let i = 0; i < 20; i++) {
arr.push({
userid: '234',
age: i,
});
}

type 和 interface 的异同

相同点

1
2
3
4
5
6
7
interface IAnimal {
name: string;
}

type Animal = {
name: string,
};

泛型

1
2
3
4
5
6
7
interface IAnimal<p = string> {
name: p;
}

type Animal<p = string> = {
name: p,
};

交叉继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Robot = {
power: number,
};

interface IRobot {
name: string;
}

interface IRobotAnimal1 extends IAnimal, IRobot {}
interface IRobotAnimal2 extends IAnimal, Robot {}
interface IRobotAnimal3 extends Animal, IRobot {}
interface IRobotAnimal4 extends Animal, Robot {}

type RobotAnimal1 = IAnimal & IRobot;
type RobotAnimal2 = IAnimal & Robot;
type RobotAnimal3 = Animal & IRobot;
type RobotAnimal4 = Animal & Robot;

实现

1
2
3
4
5
6
7
class Dog implements IAnimal {
name: string = 'doge';
}

class Cat implements Animal {
name: string = 'cat cat';
}

继承类

1
2
3
4
5
6
7
8
9
10
11
class Control {
private state: any;
}

interface ISelectableControl extends Control {
select(): void;
}

type SelectableControl = Control & {
select(): () => void;
};

函数

1
2
3
4
5
6
7
8
9
10
11
12
type Bark = (x: string) => void;

interface IBark {
(x: string): void;
}

// 函数 泛型
type Bark1 = <T = Animal>(x: T) => void;

interface IBark1 {
<T = Animal>(x: T): void;
}

递归声明

1
2
3
4
5
6
7
8
9
type Tree<P> = {
node: P,
leafs: Tree<P>[],
};

interface ITree<P> {
node: P;
leafs: Tree<P>[];
}

可索引的

1
2
3
4
5
6
7
type StringRecord = {
[index: string]: number,
};

interface IStringRecord {
[index: string]: number;
}

不同点
只能使用 type 来别名基本类型

1
2
3
4
5
6
7
type NewNumber = number;

interface INewNumber extends number {}
// 'number' only refers to a type, but is being used as a value here.

interface INewNumber extends Number {}
// 这样写是可以的,但是不要忘记 1 instanceof Number === false

元组
不能使用 interface 声明元组

1
2
3
4
5
6
7
8
9
10
11
type Tuples = [number, number];

interface ITuples {
0: number;
1: number;
}

[1, 2, 3] as Tuples;
// Conversion of type '[number, number, number]' to type 'Tuples' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Types of property 'length' are incompatible.

[1, 2, 3] as ITuples;

不相关的合集
只有 type 可以做不相关合集

1
type SomeAnimal = { type: Dog } | { type: Cat };

且不能对 不相关集合不能使用 extends 关键字

1
2
interface ISomeAnimal extends SomeAnimal {}
// An interface can only extend an object type or intersection of object types with statically known members

每个作用域只能声明一次类型

1
2
3
type Once = { a: string };
type Once = { b: string };
// Duplicate identifier 'Once'

可以在每个作用域中多次声明接口(会进行声明合并)

1
2
3
4
5
6
interface IOnce {
a: string;
}
interface IOnce {
b: string;
}

react 中的常用类型

React.FC | React.FunctionComponent

表示函数组件的类型

1
const MyComponent: React.FC<Props> = function(p) {...}

React.Component

表示类组件的类型

1
class MyComponent extends React.Component<Props, State> { ...

React.ComponentType

表示(React.FC | React. Component)的联合的类型-在 HOC 中使用

1
2
3
const withState = <P extends WrappedComponentProps>(
WrappedComponent: React.ComponentType<P>,
) => { ...

React.ComponentProps

获取指定组件 XXX 的 Props 类型(警告: 不能使用静态声明的默认道具和通用道具)

1
type MyComponentProps = React.ComponentProps<typeof MyComponent>;

React.ReactElement | JSX.Element

类型表示 DOM 组件(例如

)或自定义的组件(例如 )

1
const elementOnly: React.ReactElement = <div /> || <MyComponent />;

React.ReactNode

表示任何可能类型的 React 节点( ReactElement + 基本 JS 类型)

1
2
const elementOrPrimitive: React.ReactNode = 'string' || 0 || false || null || undefined || <div /> || <MyComponent />;
const Component = ({ children: React.ReactNode }) => ...

React.CSSProperties

JSX 中输入表示样式对象的类型

1
2
const styles: React.CSSProperties = { flexDirection: 'row', ...
const element = <div style={styles} ...

React.HTMLProps

表示指定 HTML 元素的类型-用于扩展 HTML 元素

1
2
3
const Input: React.FC<Props & React.HTMLProps<HTMLInputElement>> = props => { ... }

<Input about={...} accept={...} alt={...} ... />

React.ReactEventHandler

表示泛型事件处理函数-用于声明处理事件的函数

1
2
3
const handleChange: React.ReactEventHandler<HTMLInputElement> = (ev) => { ... }

<input onChange={handleChange} ... />

React.XXXEvent

表示更具体的事件。 常见的事件: ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent.

1
2
3
const handleChange = (ev: React.MouseEvent<HTMLDivElement>) => { ... }

<div onMouseMove={handleChange} ... />

在上面的代码中反应。 鼠标事件  React.MouseEvent  是鼠标事件的类型,这个事件发生在  HTMLDivElement  上

ts 声明文件基本写法

全局示例

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
// global-lib.js
function globalLib(option) {
console.log(option);
}
globalLib.version = '1.0.0';
globalLib.doSomething = function () {
console.log('global do something');
};
// global.d.ts
declare function globalLib(option: globalLib.Option): void;

declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Option {
[key: string]: any;
}
}

// global.d.ts
declare function globalLib(option: globalLib.Option): void;

declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Option {
[key: string]: any;
}
}

模块示例

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// module-lib.js
const version = '1.0.0'
const doSomething = function() {
console.log('module do something')
}
function moduleLib(option) {
console.log(option)
}

moduleLib.version = version
moduleLib.doSomething = doSomething

module.exports = moduleLib


// module-lib.d.ts
declare function moduleLib(option: Option): void

interface Option {
[key: string]: any
}

declare namespace moduleLib {
const version: string
function doSomething(): void
}

export = moduleLib



umd 模块示例

// umd-lib.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory)
} else if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else {
root.umdLib = factory()
}
}(this, function() {
return {
version: '1.0.0',
doSomething() {
console.log('umd do something')
}
}
}))


// umd-lib.d.ts
declare namespace umdLib {
const version: string
function doSomething(): void
}

// umd 模块必须写此行
export as namespace umdLib

export = umdLib

前置知识:

declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
export 导出变量
export namespace 导出(含有子属性的)对象
export default ES6 默认导出
export = commonjs 导出模块
export as namespace UMD 库声明全局变量
declare global 扩展全局变量
declare module 扩展模块
/// 三斜线指令

从 generator 到 async/await

协程

协程是一种比线程更加轻量级的存在。可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程,比如当前执行的是 A 协程,要启动 B 协程,那么 A 协程就需要将主线程的控制权交给 B 协程,这就体现在 A 协程暂停执行,B 协程恢复执行;同样,也可以从 B 协程中启动 A 协程。通常,如果从 A 协程启动 B 协程,我们就把 A 协程称为 B 协程的父协程

协程有点像函数,又有点像线程。它的运行流程大致如下。

第一步,协程 A 开始执行。

第二步,协程 A 执行到一半,进入暂停,执行权转移到协程 B。

第三步,(一段时间后)协程 B 交还执行权。

第四步,协程 A 恢复执行。

上面流程的协程 A,就是异步任务,因为它分成两段(或多段)执行。

举例来说,读取文件的协程写法如下。

1
2
3
4
5
function asnycJob() {
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}

上面代码的函数 asyncJob 是一个协程,在执行到其中的 yield 命令处时,执行权将交给其他协程。也就是说,yield 命令是异步两个阶段的分界线。

协程遇到 yield 命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。它的最大优点,就是代码的写法非常像同步操作,如果去除 yield 命令,简直一模一样。

Generator

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

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
// generator 函数
function* foo() {
let response1 = yield fetch('http://back.avatar.movie.test.sankuai.com/');
console.log('response1');
console.log(response1);
let response2 = yield fetch('http://back.avatar.movie.test.sankuai.com/');
console.log('response2');
console.log(response2);
}

// 执行 foo 函数的代码
let gen = foo();
function getGenPromise(gen) {
return gen.next().value;
}
getGenPromise(gen)
.then(response => {
console.log('response1');
console.log(response);
return getGenPromise(gen);
})
.then(response => {
console.log('response2');
console.log(response);
});
  • 首先执行的是let gen = foo(),创建了 gen 协程。
  • 然后在父协程中通过执行 gen.next 把主线程的控制权交给 gen 协程。
  • gen 协程获取到主线程的控制权后,就调用 fetch 函数创建了一个 Promise 对象 response1,然后通过 yield 暂停 gen 协程的执行,并将 response1 返回给父协程。
  • 父协程恢复执行后,调用 response1.then 方法等待请求结果。
  • 等通过 fetch 发起的请求完成之后,会调用 then 中的回调函数,then 中的回调函数拿到结果之后,通过调用 gen.next 放弃主线程的控制权,将控制权交 gen 协程继续执行下个请求。

通过 Generator 和 Promise 相互配合执行,达到了将异步操作以同步方式书写的目的。不过通常,我们把执行生成器的代码封装成一个函数,并把这个执行生成器代码的函数称为执行器

(可参考著名的 co 框架),如下面这种方式:

1
2
3
4
5
6
7
8
9
function* foo() {
let response1 = yield fetch('https://www.geekbang.org')
console.log('response1')
console.log(response1)
let response2 = yield fetch('https://www.geekbang.org/test')
console.log('response2')
console.log(response2)
}
co(foo());

通过使用生成器配合执行器,就能实现使用同步的方式写出异步代码了,这样也大大加强了代码的可读性。

co 源码分析

Co 核心代码(删去了非核心代码)

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
function co(gen) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
onFulfilled();

function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(
new TypeError(
'You may only yield a function, promise, generator, array, or object, ' +
'but the following object was passed: "' +
String(ret.value) +
'"'
)
);
}
}

这儿,在给 co 传入一个generator函数后,co 会将其自动启动。然后调用onFulfilled函数。在onFulfilled函数内部,首先则是获取 next 的返回值。交由next函数处理。 而next函数则首先判断是否完成,如果这个 generator 函数完成了,返回最终的值。否则则将yield后的值,转换为Promise。最后,通过Promise的 then,并将onFulfilled函数作为参数传入。

1
2
3
if (value && isPromise(value)) {
return value.then(onFulfilled, onRejected);
}

而在generator中,yield句本身没有返回值,或者说总是返回undefined 而 next 方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。同时通过onFulfilled函数,则可以实现自动调用。这也就能解释为什么 co 基于Promise。且能自动执行了。

Async/await

async 到底是什么?根据 MDN 定义,async 是一个通过异步执行隐式返回 Promise 作为结果的函数。

对 async 函数的理解,需要重点关注两个词:异步执行隐式返回 Promise

1
2
3
4
5
async function foo() {
return 2;
}
// async 函数返回一个 promise
console.log(foo()); // Promise {<resolved>: 2}

await 到底是什么?

1
2
3
4
5
6
7
8
9
async function foo() {
console.log(1);
let a = await 100;
console.log(a);
console.log(2);
}
console.log(0);
foo();
console.log(3);

根据上面的代码来分析 async/await 的执行流程。

首先,执行console.log(0)这个语句,打印出来 0。

  1. 执行 console.log(0)
  2. 执行 foo 函数,foo 是 async 函数,js 引擎保留当前的调用栈等信息
  3. 执行 foo 函数中 console.log(1)
  4. 执行 await 100,当遇到 await 100语句,js 引擎默认创建一个 promise 对象,大致代码如下:
1
2
3
let promise_ = new Promise((resolve,reject){
resolve(100)
})
  1. js 引擎执行到 resolve(10)将任务提交给微任务队列
  2. 然后 JavaScript 引擎会暂停当前协程的执行,将主线程的控制权转交给父协程执行,同时会将 promise_ 对象返回给父协程
  3. 父协程拿到主线程控制权,做的一件事是调用 promise_.then 来监控 promise 状态的改变
  4. 继续执行console.log(3)
  5. 父协程将执行结束,在结束之前,检查微任务队列,然后执行微任务队列
  6. 执行resolve(100),触发 promise_.then 中的回调函数:
1
2
3
4
promise_.then(value => {
// 回调函数被激活后
// 将主线程控制权交给 foo 协程,并将 vaule 值传给协程
});
  1. 将主线程的控制权交给 foo 函数的协程,并同时将 value 值传给该协程
  2. foo 协程激活之后,会把刚才的 value 值赋给了变量 a
  3. 执行 foo 函数的后续语句,执行完成之后,将控制权归还给父协程。
  4. 完毕。

以上就是 await/async 的执行流程。正是因为 async 和 await 在背后为我们做了大量的工作,所以我们才能用同步的方式写出异步代码来。


Co 源码详细分析

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* 执行 generator function 或者 generator,
* 返回一个 Promise
* @param {Function} fn
* @return {Promise}
* @api public
*/

function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1);

// we wrap everything in a promise to avoid promise chaining,
// 把传进来的所有东西都转为 promise
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function (resolve, reject) {
// 执行 gen 函数
if (typeof gen === 'function') gen = gen.apply(ctx, args);
// gen函数返回的 gen 指针不存在或 gen.next 不是函数(意味着 gen 不是 Generator函数) 则返回空值
if (!gen || typeof gen.next !== 'function') return resolve(gen);

onFulfilled();

/**
* @param {Mixed} res
* @return {Promise}
* @api private
* 把每次 yield 之后的异步函数的返回结果当做参数传回 generator 函数
* eg. let a = yeild b
* 此时经过 onFulfilled 函数的处理 a === b()
*/
function onFulfilled(res) {
var ret;
try {
/**
* gen.next(res),则是向generator函数传参数,作为yield的返回值
* ret 是 gen.next() 返回的对象
* ---
* yield 语句本身没有返回值,或者说总是返回undefined。
* next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
*/
ret = gen.next(res);
} catch (e) {
return reject(e);
}
// 每完成一次 yield,把返回的对象交给 next() 处理
next(ret);
}

/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}

/**
* Get the next value in the generator,
* return a promise.
* 把 gen.next() 返回对象中的 value 变为 promise
*
* @param {Object} ret
* @return {Promise}
* @api private
*/

function next(ret) {
/**
* 如果这个generator函数完成了,返回最终的值
* 假设我们写的 generator函数没有 return someValue, 在所有yield完成后,调用next()会返回{valu: undefined, done: true}
* 所以需要手动return一个值。这样最后的value才不是undefined
*/
if (ret.done) return resolve(ret.value);
// 这个generator函数还没结束, 就统一交给 toPromise() 处理
var value = toPromise.call(ctx, ret.value);
// 这里value.then(onFulfilled, onRejected),实际上已经调用并传入了 onFulfilled, onRejected 两个参数。
// 把 onFulfilled 函数传入 value
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(
new TypeError(
'You may only yield a function, promise, generator, array, or object, ' +
'but the following object was passed: "' +
String(ret.value) +
'"'
)
);
}
});
}

剩下的(thunkToPromise, arrayToPromise)这些辅助函数就不写了

react hooks 初步

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码”钩”进来。 React Hooks 就是那些钩子。

react 约定, hook 一律使用 use 前缀命名,便于识别
常用 hook

useState()
useContext()
useEffect()
useReducer()
useState

useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。

1
2
3
4
function Button() {
const [count, setCount] = useState(0);
return <button onClick={setCount(count + 1)}>+1</button>;
}

useState()接受初始值,返回一个数组,数组第一个是当前状态,第二个是更新状态的函数。

useContext

useContext()用于组件间共享状态。

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
// 创建一个 context 对象
const ctxDep = {name: '王花花'}
const Ctx = React.creatContext(ctxDep)
// 在组件中使用
function App() {
return (
// 下级组件使用 useContext 获取的值是 ctxDep.name
<Ctx.Provider value={ctxDep.name}>
<Sub />
<Sub2 />
</Ctx.Provider>
)
}
function Sub() {
// useContext 获取的是距离当前组件最近的 <MyContext.Provider> 的 value prop 。
// 此处的就是 ctxDep.name
const name = useContext(Ctx)
return (
<div>name: {name}</div>
)
}
function Sub2() {
const name = useContext(Ctx)
...
}

useEffect

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。

1
2
3
useEffect(() => {
// Async Action
}, [dependencies]);

useEffect()接受两个参数。第一个是一个函数,操作的代码放在里面(常见的是异步操作)。第二个是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行 useEffect()。 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect

useEffect 清除操作

在 useEffect 中返回一个函数去执行清除操作, React 会在组件卸载的时候执行清除操作。React 会在执行当前 effect 之前对上一个 effect 进行清除。

1
2
3
4
5
6
useEffect(() => {
...
return function cleanFun() {
...
}
}, [])

useReducer

useReducers()钩子用来引入 Reducer 功能。升级版 useState

1
const [state, dispatch] = useReducer(reducer, initialState);

useReducer()接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是与其配套的发送 action 的 dispatch 函数。

使用 useReducer()接受的 reducer 形式如(state, action) => newState

惰性初始化

使用 useReducer 的第三个参数来设置初始值

1
2
3
const initialState = 1;
// init 是函数,返回 {count: 1}
const [state, dispatch] = useReducer(reducerCount, initialState, init);

如果 useReducer 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。

useMemo

用于性能优化,useMemo 返回一个值。

1
const memoizedValue = useMemo(() => dosomething(a, b), [a, b]);

useMemo 会在渲染期执行,但除了第一次外,只会在依赖项发生改变后才会重新执行。

useCallback

useCallback()返回一个函数, 在依赖值没有发生改变时不触发重新渲染。

1
2
3
4
5
6
// 仅当 count 的值发生改变时,memoCallback 才触发组件重新渲染
function Foo() {
const [count, setCount] = useState(0);
const memoizedHandleClick = useCallback(() => console.log(`点击: ${count}`), [count]);
return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}

useRef

1
2
3
4
5
const refContainer = useRef(initialValue);
return (
// 此时 refContainer.current 指向下面这个 button
<button ref={refContainer}></button>
);

useLayoutEffect

效果 useEffect 相同,但会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

react 生命周期

挂载阶段

React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载。

  1. constructor: 所有关于组件自身的状态的初始化工作都会放在这里面去做

  2. static getDerivedStateFromProps(nextProps, prevState): 一个静态方法,所以不能在这个函数里面使用 this,这个函数有两个参数 props 和 state,分别指接收到的新参数和当前的 state 对象,这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null。

    该函数会在挂载时,接收到新的 props,调用了 setState 和 forceUpdate 时被调用

  3. UNSAFE_componentWillMount:组件挂载开始之前,也就是在组件调用  render  方法之前调用。进行组件的启动工作,例如 Ajax 数据拉取、定时器的启动。

  4. render:React 中最核心的方法,一个组件中必须要有这个方法

    返回的类型有以下几种:

    • 原生的 DOM,如 div
    • React 组件
    • Fragment(片段)
    • Portals(插槽)
    • 字符串和数字,被渲染成 text 节点
    • Boolean 和 null,不会渲染任何东西

    render 函数是纯函数,里面只做一件事,就是返回需要渲染的东西,不应该包含其它的业务逻辑,如数据请求,对于这些业务逻辑请移到 componentDidMount 和 componentDid Update 中

  5. componentDidMount:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用。

更新阶段

除了挂载阶段,还有一种“更新阶段”就是  setState  导致 React.js 重新渲染组件并且把组件的变化应用到 DOM 元素上的过程,这是一个组件的变化过程

  1. UNSAFE_componentWillReceiveProps(nextProps):组件从父组件接收到新的  props  之前调用。
  2. static getDerivedStateFromProps
  3. shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回  false  组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
  4. UNSAFE_componentWillUpdate():组件开始重新渲染之前调用。
  5. render()
  6. getSnapshotBeforeUpdate(prevProps, prevState):这个方法在 render 之后,componentDidUpdate 之前调用,有两个参数 prevProps 和 prevState,表示之前的属性和之前的 state,这个函数有一个返回值,会作为第三个参数传给 componentDidUpdate,如果你不想要返回值,请返回 null,不写的话控制台会有警告。
  7. componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

卸载阶段

componentWillUnmount:组件对应的 DOM 元素从页面中删除之前调用。组件从页面上销毁的时候,有时候需要一些数据的清理,例如定时器的清理,就会放在这里面去做。

参考:

React v16.4.0:你可能并不需要派生状态(Derived State)

对 React v16.4 生命周期的理解

react 学习笔记

  • _自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头_。

  • 没有经过特殊处理的话,这些  on\*  的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上

  • React.js 的事件监听方法需要手动  bind  到当前实例,这种模式在 React.js 中非常常用。

  • 当我们要改变组件的状态的时候,不能直接用  this.state = xxx  这种方式来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。所以,一定要使用 React.js 提供的  setState  方法,它接受一个对象或者函数作为参数

  • React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的  setState  都进行合并以后再重新渲染组件。

  • 可以通过给组件添加类属性  defaultProps  来配置默认参数。

  • props  一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的  props,从而达到更新的效果。

  • 没有  state  的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)

  • 对于用表达式套数组罗列到页面上的元素,都要为每个元素加上  key  属性,这个  key  必须是每个元素唯一的标识


受控组件:

React.js 认为所有的状态都应该由 React.js 的 state 控制,只要类似于<input />、<select />、<textarea>这样的输入控件被设置了  value  值,那么它们的值永远以被设置的值为准。值不变,value  就不会变化。

在 React.js 当中必须要用  setState  才能更新组件的内容,所以我们需要做的就是:监听输入框的  onChange  事件,然后获取到用户输入的内容,再通过  setState  的方式更新  state  中的  username,这样  input  的内容才会更新。


子组件传递消息给父组件

父组件只需要通过  props  给子组件传入一个回调函数。子组件调用  props  中的回调函数并且将  state  传入该函数即可。

举例:


状态提升

当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用  props  传递数据或者函数来管理这种依赖或着影响的行为。

对于不会被多个组件依赖和影响的状态(例如某种下拉菜单的展开和收起状态),一般来说只需要保存在组件内部即可,不需要做提升或者特殊的管理。


操作 DOM

1. 使用回调: 在元素上加一个  ref  属性,这个属性值是一个函数,这个节点挂载到页面上后,这个 dom 节点会作为参数传给这个函数

1
2
// 把这个 div 保存到 this 上
<div ref={div => (this.div = div)}></div>

然后我们就可以在  componentDidMount中 或之后使用这个 DOM 元素

2. 使用 React.createRef()

1
2
3
4
5
6
7
8
9
10
11
// 在 constructor 中声明
this.third = React.createRef();
// 在 render 函数中:
<input type="text" defaultValue="Third" ref={this.third} />;
// 获取 ref
this.third.current;

// 在 render 函数里面
<input type="text" defaultValue="First" ref="first" />;
// 获取 ref
this.refs.first;
  • _能不用  ref  就不用_。特别是要避免用  ref  来做 React.js 本来就可以帮助你做到的页面自动更新的操作和事件监听。多余的 DOM 操作其实是代码里面的“噪音”,不利于我们理解和维护。
  • 组件标签也可以加上  ref,此时获取的是组件实例。

props.children

组件标签也能像普通的 HTML 标签那样编写内嵌的结构

1
2
3
4
5
6
7
8
render() {
return (
<Card>
<Test />
</Card>
)
}
// Card 组件中能通过 this.props.children 获取到 <Test />

React.js 默认就支持这种写法,所有嵌套在组件中的 JSX 结构都可以在组件内部通过  props.children  获取到,props.children  是一个数组,React.js 就是把我们嵌套的 JSX 元素一个个都放到数组当中。


PropTypes

引入  prop-types

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class a extends Component {
static propTypes = {
username: PropTypes.string.isRequired,
...
PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
}
}

通过  PropTypes  给组件的参数做类型限制,可以在帮助我们迅速定位错误,这在构建大型应用程序的时候特别有用;另外,给组件加上  propTypes,也让组件的开发、使用更加规范清晰。