什么是React
React 用于构建 Web 和原生交互界面的库。
- 是一个用于构建用户界面的 JAVASCRIPT 库。
- 主要用于构建 UI,很多人认为 React 是 MVC 中的 V(视图)。
- React 起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。
- React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。
特点
- 声明式设计 −React采用声明范式,可以轻松描述应用。
- 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
- 灵活 −React可以与已知的库或框架很好地配合。
- JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
常见的react文件后缀名
.js
: 这是 JavaScript 文件。这是前端开发的基础,它包含了逻辑和函数等代码。.jsx
: 这是 JavaScript XML 的缩写,它允许你在 JavaScript 文件中书写 XML 或 HTML 风格的语法。这在 React 中广泛地应用,因为 React 组件通常就是用 JSX 编写的。.ts
: 这是 TypeScript 文件。TypeScript 是 Microsoft 开发的一种静态类型检查的 JavaScript 扩展语言,它可以帮助开发者编写更健壮的代码,并提供了更好的编辑器支持。.tsx
: 这是 TypeScript 的 JSX 版本。如果你在 TypeScript 文件中需要书写 JSX,则需要将文件后缀名设置为 .tsx。.css
: 这是层叠样式表(Cascading Style Sheets)文件,用于描述 HTML 文档的外观和格式。.less
: 这是 LESS 文件,LESS 是一种 CSS 预处理语言,它添加了变量、混合、函数等特性,使得 CSS 更易于维护和扩展。Ant Design 的样式就是用 LESS 编写的。.json
: 这是 JSON (JavaScript Object Notation) 文件,它是一种轻量级的数据交换格式,非常适合于数据的存储和交换。.md / .markdown
: 这是 Markdown 文件,它是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。
ES6
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。
ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
箭头函数
作用
更简洁的函数表达式,并且可以解决 this
关键字在不同环境中可能变化的问题。
JavaScript 中的 this 关键字是特别的,它的值取决于函数如何被调用:
- 在传统的函数中,this 的值可以变化。
- 而在箭头函数中,this 是在函数创建时就被绑定的,它的值与离它最近的包围函数(即它的词法环境)的 this 值相同。
举例说明箭头函数是怎么解决this
关键字可能变化的问题。
const myObject = {
property: 'Hello',
method: function() {
console.log(this.property);
setTimeout(function() {
console.log(this.property);
}, 1000);
}
}
myObject.method();
上述代码中的 setTimeout 内部的函数是一个匿名函数,当它被调用时,它的 this
实际上不指向 myObject
,而是指向全局对象(在浏览器中为 window
)。所以这个代码会先打印出 “Hello”,然后是 undefined
。
而改用箭头函数呢?
const myObject = {
property: 'Hello',
method: function() {
console.log(this.property);
setTimeout(() => {
console.log(this.property);
}, 1000);
}
}
myObject.method();
由于箭头函数没有自己的 this
,它会从包围它的 method
函数那里获取 this
,即 myObject
对象。因此,当 setTimeout
中的箭头函数被调用时,this.property
正确地引用了 myObject
的 property
属性。所以这次会连续打印出两个 “Hello”。
因此,箭头函数通过继承其包围函数的 this 上下文,解决了 this 在不同执行环境中可能变化的问题。
省略规则
一个基本的箭头函数的语法如下:
const functionName = (parameters) => {
// function body
}
其中 (parameters)
是你要传递给函数的参数列表,=>
是箭头符号,表示这是一个箭头函数,而 {}
中间的部分就是函数体,也就是当调用这个函数时会执行的代码。
箭头函数有一些编写更简洁的省略规则:
- 参数省略规则:如果箭头函数只有一个参数,那么可以省略参数两边的括号。例如:
const square = x => {
return x * x;
}
- 函数体省略规则:如果函数体只有一条语句,并且该语句就是返回值,那么可以省略 {} 和 return 关键字。例如:
const square = x => x * x;
其他更多的省略形式:
var elements = ["Hydrogen", "Helium", "Lithium", "Beryllium"];
elements.map(function (element) {
return element.length;
}); // 返回数组:[8, 6, 7, 9]
// 上面的普通函数可以改写成如下的箭头函数
elements.map((element) => {
return element.length;
}); // [8, 6, 7, 9]
// 当箭头函数只有一个参数时,可以省略参数的圆括号
elements.map(element => {
return element.length;
}); // [8, 6, 7, 9]
// 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
elements.map((element) => element.length); // [8, 6, 7, 9]
// 在这个例子中,因为我们只需要 `length` 属性,所以可以使用参数解构
// 需要注意的是字符串 `"length"` 是我们想要获得的属性的名称,而 `lengthFooBArX` 则只是个变量名,
// 可以替换成任意合法的变量名
elements.map(({ length: lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]
模块
ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。
ES6 的模块化分为导出(export)
与导入(import)
两个模块。
基本用法
模块导入导出各种类型的变量,如字符串,数值,函数,类。
- 导出的函数声明与类声明必须要有名称(export default 命令另外考虑)。
- 不仅能导出声明还能导出引用(例如函数)。
- export 命令可以出现在模块的任何位置,但必需处于模块顶层。
- import 命令会提升到整个模块的头部,首先执行。
// 导出例子
/*-----export [test.js]-----*/
let myName = "Tom";
let myAge = 20;
let myfn = function(){
return "My name is" + myName + "! I'm '" + myAge + "years old."
}
let myClass = class myClass {
static a = "yeah!";
}
export { myName, myAge, myfn, myClass }
注意:
- 建议使用大括号指定所要输出的一组变量写在文档尾部,明确导出的接口。
- 函数与类都需要有对应的名称,导出文档尾部也避免了无对应名称。
/*-----import [xxx.js]-----*/
import { myName, myAge, myfn, myClass } from "./test.js";
console.log(myfn());// My name is Tom! I'm 20 years old.
console.log(myAge);// 20
console.log(myName);// Tom
console.log(myClass.a );// yeah!
可以使用as
重新定义变量名,解决不同模块导出接口名称命名重复的问题
import
- 只读属性。可以改写 import 变量类型为对象的属性值,不能改写 import 变量类型为基本类型的值。
- 单例模式。多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。import 同一模块,声明不同接口引用,会声明对应变量,但只执行一次 import 。
举例说明:
- 只读属性,不能更改导入为
基本类型
变量
// 在 module.js 文件中
export const a = 1;
// 在另一个文件中
import { a } from './module.js';
a = 2; // 错误!`a` 是只读的
- 只读属性,可以修改导入为
引用类型的属性
// 在 module.js 文件中
export const obj = { prop: 'hello' };
// 在另一个文件中
import { obj } from './module.js';
obj.prop = 'world'; // 正确。改写 obj 对象的属性值是允许的
obj = {}; // 错误!`obj` 是只读的,不能改写整个对象
- 只读属性。如果导入的变量类型是对象(包括数组和函数),你可以修改其属性值,但你不能重新分配整个对象
// 在 module.js 文件中
export const arr = [1, 2, 3];
// 在另一个文件中
import { arr } from './module.js';
arr[0] = 4; // 正确。改写 arr 数组的元素值是允许的
arr = []; // 错误!`arr` 是只读的,不能改写整个数组
- 单例模式。
import { a } "./xxx.js";
import { a } "./xxx.js";
// 相当于 import { a } "./xxx.js";
import { a } from "./xxx.js";
import { b } from "./xxx.js";
// 相当于 import { a, b } from "./xxx.js";
export default
在一个文件或模块中,可以使用 export 关键字导出多个变量或函数,但 export default 只能用一次
。这是因为 export default 导出的成员将作为模块的默认导出,当其他模块只使用 import 语句而不加花括号 {} 导入时,就会得到这个默认导出的值。
- export default 中的 default 是对应的
导出接口变量
。 - 通过 export 方式导出,在导入时要加{ },export default 则不需要。
- export default 向外暴露的成员,可以使用任意变量来接收。
假设我们有一个名为 module.js 的模块:
// module.js
export const a = 1;
export const b = 2;
export default function() {
console.log('default function');
}
现在我们来导入这个模块:
// 在另一个文件中
import myFunc, { a, b } from './module.js';
console.log(a); // 输出:1
console.log(b); // 输出:2
myFunc(); // 输出:'default function'
在以上示例中,a 和 b 都通过 export 语句导出,所以在导入它们时需要包裹在 {} 中。而默认导出的函数没有具体的名字,我们在导入时可以自由地给它命名(在上面的例子中,我们把它命名为 myFunc),并且不需要放在 {} 中。
Promise
promise
是 JavaScript ES6 引入的概念,是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
Promise 状态
Promise 异步操作有三种状态:
- pending(进行中)
- fulfilled(已成功)
- rejected(已失败)
除了异步操作的结果,任何其他操作都无法改变这个状态。
Promise 对象只有:
- 从 pending 变为 fulfilled
- 从 pending 变为 rejected
只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
状态的缺点
- 无法取消 Promise ,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise构造方法
new Promise(executor)
其中executor
在构造函数中执行的 function。它接收两个函数作为参数:resolveFunc
和 rejectFunc
。executor
中抛出的任何错误都会导致 Promise 被拒绝,并且返回值将被忽略。
function executor(resolveFunc, rejectFunc) {
// 通常,`executor` 函数用于封装某些接受回调函数作为参数的异步操作,比如上面的 `readFile` 函数
}
- 在构造函数生成新的
Promise
对象时,它还会生成一对相应的resolveFunc
和rejectFunc
函数;它们与Promise
对象“绑定”在一起。 executor
通常会封装某些提供基于回调的 API 的异步操作。回调函数(传给原始回调 API 的函数)在executor
代码中定义,因此它可以访问resolveFunc
和rejectFunc
。executor
是同步调用的(在构造Promise
时立即调用),并将resolveFunc
和rejectFunc
函数作为传入参数。executor
中的代码有机会执行某些操作。异步任务的最终完成通过resolveFunc
或rejectFunc
引起的副作用与Promise
实例进行通信。这个副作用让Promise
对象变为“已解决”状态。- 如果先调用
resolveFunc
,则传入的值将解决。 - 如果先调用
rejectFunc
,则Promise
立即变为已拒绝状态 - 一旦
resolveFunc
或rejectFunc
中的一个被调用,Promise
将保持解决状态。只有第一次调用resolveFunc
或rejectFunc
会影响Promise
的最终状态,随后对任一函数的调用都不能更改兑现值或拒绝原因,也不能将其最终状态从“已兑现”转换为“已拒绝”或相反。 - 如果
executor
抛出错误,则Promise
被拒绝。但是,如果resolveFunc
或rejectFunc
中的一个已经被调用(因此Promise
已经被解决),则忽略该错误。 - 解决
Promise
不一定会导致Promise
变为已兑现或已拒绝(即已敲定)。Promise
可能仍处于待定状态,因为它可能是用另一个thenable
对象解决的,但它的最终状态将与已解决的thenable
对象一致。
- 如果先调用
- 一旦
Promise
敲定,它会(异步地)调用任何通过then()、catch() 或 finally()
关联的进一步处理程序。最终的兑现值或拒绝原因,在调用时作为输入参数传给兑现和拒绝处理程序。
一个比较全面的例子
// 为了尝试错误处理,使用“阈值”值会随机地引发错误。
const THRESHOLD_A = 8; // 可以使用 0 使错误必现
function tetheredGetNumber(resolve, reject) {
setTimeout(() => {
const randomInt = Date.now();
const value = randomInt % 10;
if (value < THRESHOLD_A) {
resolve(value);
} else {
reject(`太大了:${value}`);
}
}, 500);
}
function determineParity(value) {
const isOdd = value % 2 === 1;
return { value, isOdd };
}
function troubleWithGetNumber(reason) {
const err = new Error("获取数据时遇到问题", { cause: reason });
console.error(err);
throw err;
}
function promiseGetWord(parityInfo) {
return new Promise((resolve, reject) => {
const { value, isOdd } = parityInfo;
if (value >= THRESHOLD_A - 1) {
reject(`还是太大了:${value}`);
} else {
parityInfo.wordEvenOdd = isOdd ? "奇数" : "偶数";
resolve(parityInfo);
}
});
}
new Promise(tetheredGetNumber)
.then(determineParity, troubleWithGetNumber)
.then(promiseGetWord)
.then((info) => {
console.log(`得到了:${info.value}, ${info.wordEvenOdd}`);
return info;
})
.catch((reason) => {
if (reason.cause) {
console.error("已经在前面处理过错误了");
} else {
console.error(`运行 promiseGetWord() 时遇到问题:${reason}`);
}
})
.finally((info) => console.log("所有回调都完成了"));
then 方法
then 方法接收两个函数作为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
链式调用
then()
函数会返回一个和原来不同的新的 Promise:
const promise = doSomething();
const promise2 = promise.then(successCallback, failureCallback);
promise2
不仅表示 doSomething()
函数的完成,也代表了你传入的 successCallback
或者 failureCallback
的完成。
每一个 Promise 都代表了链中另一个异步过程的完成。
此外,then 的参数是可选的,catch(failureCallback) 等同于 then(null, failureCallback)——所以如果你的错误处理代码对所有步骤都是一样的,你可以把它附加到链的末尾:
doSomething()
.then(function (result) {
return doSomethingElse(result);
})
.then(function (newResult) {
return doThirdThing(newResult);
})
.then(function (finalResult) {
console.log(`得到最终结果:${finalResult}`);
})
.catch(failureCallback);
可以用箭头函数改写:
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log(`得到最终结果:${finalResult}`);
})
.catch(failureCallback);
注意:用箭头函数改写一定要有返回值
,否则,回调将无法获取上一个 Promise 的结果。
generator 函数
Generator 有两个区分于普通函数的部分:
- 在 function 后面,函数名之前有个 * ;
- 函数内部有 yield 表达式。
生成器函数在执行时能暂停,后面又能从暂停处继续执行
function*
function*
这种声明方式 (function关键字后跟一个星号)会定义一个生成器函数 (generator function),它返回一个 Generator 对象。
语法
function* name([param[, param[, ... param]]]) { statements }
- name 函数名
- param 要传递给函数的一个参数的名称,一个函数最多可以有 255 个参数
- statements 普通 JS 语句
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 ( iterator )
对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。
如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i) {
yield i;
yield* anotherGenerator(i); // 移交执行权
yield i + 10;
}
var gen = generator(10);
console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20
async 函数
async
是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。
async 函数
是使用async
关键字声明的函数。async 函数
是 AsyncFunction 构造函数
的实例,并且其中允许使用 await
关键字。async
和 await
关键字让我们可以用一种更简洁的方式写出基于 Promise
的异步行为,而无需刻意地链式调用 promise
。
async
函数可能包含 0 个或者多个 await
表达式。await
表达式会暂停整个 async
函数的执行进程并出让其控制权,只有当其等待的基于 promise
的异步操作被兑现或被拒绝之后才会恢复进程。promise
的解决值会被当作该 await
表达式的返回值。使用 async/await
关键字就可以在异步代码中使用普通的 try/catch
代码块。
语法
async function name([param[, param[, ... param]]]) { statements }
- name: 函数名称。
- param: 要传递给函数的参数的名称。
- statements: 函数体语句。
async 函数一定会返回一个 promise 对象。如果一个 async 函数的返回值看起来不是 promise,那么它将会被隐式地包装在一个 promise 中。
下面两个是等价的
async function foo1() {
return 1;
}
// foo1等价于foo2
function foo2() {
return Promise.resolve(1);
}
async
函数的函数体可以被看作是由 0 个或者多个 await
表达式分割开来的。
- 从第一行代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。
- 一个不含
await
表达式的async
函数是会同步运行的。 - 如果函数体内有一个
await
表达式,async
函数就一定会异步执行。
async function foo1() {
await 1;
}
// foo1等价于foo2
function foo2() {
return Promise.resolve(1).then(() => undefined);
}
async/await 和 Promise/then 对比
大多数 async 函数也可以使用 Promises 编写。但是,在错误处理方面,async 函数更容易捕获异常错误。
- 使用async/await可以让我们编写看起来更像同步代码的异步代码,它们通常更易于阅读和理解。
- 使用.then需要更多的回调函数,可能导致“回调地狱”,但在某些情况下(例如链式调用或者处理各种可能的解决/拒绝情况)可能会更有用。
- async/await基于Promise实现,它仍然需要Promise本身提供的功能,比如Promise.all用于并行等待多个操作完成。
await 和并行
请使用 Promise.all 或者 Promise.allSettled在async中执行多个promise
function resolveAfter2Seconds() {
console.log("starting slow promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("slow");
console.log("slow promise is done");
}, 2000);
});
}
function resolveAfter1Second() {
console.log("starting fast promise");
return new Promise((resolve) => {
setTimeout(() => {
resolve("fast");
console.log("fast promise is done");
}, 1000);
});
}
async function sequentialStart() {
console.log("==SEQUENTIAL START==");
// 1. Execution gets here almost instantly
const slow = await resolveAfter2Seconds();
console.log(slow); // 2. this runs 2 seconds after 1.
const fast = await resolveAfter1Second();
console.log(fast); // 3. this runs 3 seconds after 1.
}
async function concurrentStart() {
console.log("==CONCURRENT START with await==");
const slow = resolveAfter2Seconds(); // starts timer immediately
const fast = resolveAfter1Second(); // starts timer immediately
// 1. Execution gets here almost instantly
console.log(await slow); // 2. this runs 2 seconds after 1.
console.log(await fast); // 3. this runs 2 seconds after 1., immediately after 2., since fast is already resolved
}
function concurrentPromise() {
console.log("==CONCURRENT START with Promise.all==");
return Promise.all([resolveAfter2Seconds(), resolveAfter1Second()]).then(
(messages) => {
console.log(messages[0]); // slow
console.log(messages[1]); // fast
},
);
}
async function parallel() {
console.log("==PARALLEL with await Promise.all==");
// Start 2 "jobs" in parallel and wait for both of them to complete
await Promise.all([
(async () => console.log(await resolveAfter2Seconds()))(),
(async () => console.log(await resolveAfter1Second()))(),
]);
}
sequentialStart(); // after 2 seconds, logs "slow", then after 1 more second, "fast"
// wait above to finish
setTimeout(concurrentStart, 4000); // after 2 seconds, logs "slow" and then "fast"
// wait again
setTimeout(concurrentPromise, 7000); // same as concurrentStart
// wait again
setTimeout(parallel, 10000); // truly parallel: after 1 second, logs "fast", then after 1 more second, "slow"
首先看resolveAfter2Seconds
和resolveAfter1Second
函数,它们返回一个新的Promise
,这个Promise
在一段延迟后解决,并打印出消息。
详细解释每种处理异步操作的方法:
- 顺序(Sequential):异步函数
sequentialStart
里面,首先等待resolveAfter2Seconds
解决,然后再等待resolveAfter1Second
解决。因此,这两个promise是一个接一个地解决的。 - 并发(Concurrent):在
concurrentStart
函数中,两个promise都立即开始,但是我们通过await
关键字按顺序等待它们解决。所以,在第一个promise解决之后,第二个promise可能已经解决了,因此你可以几乎立刻得到结果。 - Promise.all并发:
concurrentPromise
函数与concurrentStart
函数的行为相似,但是它使用Promise.all
来等待所有的promise都解决。当所有的promise
都解决时,.then
会被调用,并且会收到一个数组,这个数组包含了每个promise解决的值。 - 并行(Parallel):在
parallel
函数中,我们也使用Promise.all
,但是我们同时开始(并打印)两个promise的结果。这是真正的并行执行,因为两个操作是同时进行的,并且各自的结果会在它们就绪时立即打印出来。