IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> TypeScript 的应用 -> 正文阅读

[JavaScript知识库]TypeScript 的应用

1、TypeScript 简介

1.1、什么是 TypeScript

TypeScript 不是一门全新的语言,TypeScriptJavaScript 的超集,它对 JavaScript 进行了一些规范和补充。所以,学习 TypeScript 需要有 JavaScript 的基础

TypeScript 的特性:

  • TypeScriptJavaScript 的超集,它可以编译成纯 JavaScript
  • TypeScript 基于 ECMAScript 标准进行拓展,支持 ECMAScript 未来提案中的特性,如装饰器、异步功能等。
  • TypeScript 编译的 JavaScript 可以在任何浏览器运行,TypeScript 编译工具可以运行在任何操作系统上。
  • TypeScript 起源于开发较大规模 JavaScript 应用程序的需求。由微软在2012年发布了首个公开版本。

1.2、为什么要使用 TypeScript

静态类型

JavaScript 只会在运行时去做数据类型检查,而 TypeScript 作为静态类型语言,其数据类型是在编译期间确定的,编写代码的时候要明确变量的数据类型。使用 TypeScript 后,就不会出现因为数据类型错误导致的代码运行异常。

常见的 JavaScript 异常有:

Uncaught TypeError: Cannot read property
TypeError: ‘undefined’ is not an object
TypeError: null is not an object
TypeError: Object doesn’t support property
TypeError: ‘undefined’ is not a function
TypeError: Cannot read property ‘length’

三大框架支持

TypeScript 之所以能够流行起来并且保持长久的生命力,离不开前端三大框架的支持。

  • AngularTypeScript 最早的支持者,Angular 官方推荐使用 TypeScript 来创建应用。
  • React 虽然经常与 Flow 一起使用,但是对 TypeScript 的支持也很友好。
  • Vue3.0 正式版即将发布,由 TypeScript 编写。

从国内的氛围来看,由前端三大框架引领的 TypeScript 热潮已经涌来,很多招聘要求上也都有了 TypeScript的身影。

兼容 JavaScript

TypeScript 虽然严谨,但没有丧失 JavaScript 的灵活性,TypeScript 非常包容:

  • TypeScript 通过 tsconfig.json 来配置对类型的检查严格程度。
  • 可以把 .js 文件直接重命名为 .ts
  • 可以通过将类型定义为 any 来实现兼容任意类型。
  • 即使 TypeScript 编译错误,也可以生成 JavaScript 文件。

这里先简单介绍下 any 类型,后续会详细讲解。比如一个 string 类型,在赋值过程中类型是不允许改变的:

let brand: string = 'imooc'
brand = 1  // Type '1' is not assignable to type 'string'.ts(2322)
代码块12

如果是 any 类型,则允许被赋值为任意类型,这样就跟我们平时写 JavaScript 一样了:

let brand: any = 'imooc'
brand = 1 
代码块12

基于上面这些特点,一个熟悉 JavaScript 的工程师,在查阅一些 TypeScript 语法后,即可快速上手 TypeScript

2、安装 TypeScript

2.1、使用 tsc 命令编译 TypeScript 代码

安装命令:

$ npm install -g typescript
# 或使用淘宝镜像
$ cnpm i -g typescript

查看当前 typescript 版本:

$ tsc -v

创建 index.ts 文件,示例代码如下:

const msg:string = 'hello'
console.log(msg);

在当前文件所在目录运行命令:

$ tsc index.ts

会生成一个同名的 index.js 文件,然后在使用 node 命令运行 index.js 文件:

$ node index.js

运行成功即可以在控制台打印 hello 的内容。

2.2、使用 ts-node 快速编译 TypeScript

安装 ts-node :

$ npm install -g ts-node
# 或
$ cnpm i -g ts-node

使用 ts-node 可以快速运行 .ts 文件,无需先转成 .js 文件后再运行,使用 ts-node 运行 index.ts 文件:

$ ts-node index.ts

如果运行 ts-node 命令报错,提示:Error: Cannot find module '@types/node/package.json',需要安装相关依赖:

$ npm install -g tslib @types/node

2.3、使用工程化编译方案

创建 ts-project 项目的目录,在当前项目根目录下启动命令行工具,初始化 package.json文件:

$ npm init -y

package.json 中入口文件选项改为 scr/index.ts

{
    "main": "src/index.ts"
}

然后使用 tsc 命令进行初始化:

$ tsc --init

在项目根目录下就会自动创建一个 tsconfig.json 文件,它指定了用来编译这个项目的根文件和编译选项。

tsconfig.json 配置文件做修改:

{
  "compilerOptions": {
    "target": "ESNext",     /* 支持 ES6 语法 */                      
    "module": "commonjs",                     
    "outDir": "./lib",
    "rootDir": "./src",
    "declaration": true,    /* 生成相应的.d.ts文件 */
    "strict": true, 
    "strictNullChecks": false,
    "noImplicitThis": true
  },
  "exclude": ["node_modules", "lib", "**/*.test.ts"],
  "include": ["src"]
}

package.json 文件中,加入 script 命令以及依赖关系:

{
  "name": "ts-project",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "tsc": "tsc"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^13.1.1",
    "typescript": "^3.7.4"
  }
}

下载相关依赖:

$ npm install

你会看到多了一个 node_modules 文件夹和一个 package-lock.json 文件,node_modules 文件夹是项目的所有依赖包,package-lock.json 文件将项目依赖包的版本锁定,避免依赖包大版本升级造成不兼容问题。

src/index.ts 文件中编写 TypeScript 代码:

export enum TokenType {
  ACCESS = 'accessToken',
  REFRESH = 'refreshToken'
}

执行编译命令:

$ npm run tsc

这时候可以看到多了一个 lib 文件夹,里面的内容就是项目的编译结果了。

lib 文件夹中会生成两个文件:

  • index.d.ts
  • index.js

.d.ts 是声明文件,用于编写第三方类库,通过配置 tsconfig.json 文件中的 declarationtrue ,在编译时可自行生成。

3、TypeScript 基本数据类型

3.1、变量声明

TypeScriptJavaScript 的超集,同 JavaScript 一样,声明变量可以采用下面三个关键字:

  • var
  • let
  • const

详细解释可以查看 ES6 的相关教程。

TypeScript 中变量声明语法:冒号 : 前面是变量名称,后面是变量类型。例如:

let i:number = 1

3.2、TypeScript 基本类型

TypeScript 中的类型有:

  • 原始类型
    • boolean
    • number
    • string
    • void
    • null
    • undefined
    • bigint
    • symbol
  • 元组 tuple
  • 枚举 enum
  • 任意 any
  • unknown
  • never
  • 数组 Array
  • 对象 object

布尔类型

表示逻辑值:truefalse,声明一个布尔类型:

const boo: boolean = false
boo = 0 //报错

const done: boolean = Boolean(0)

数字类型

表示双精度 64 位浮点值,可以用来表示整数和分数。

let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744;    // 八进制
let decLiteral: number = 6;    // 十进制
let hexLiteral: number = 0xf00d;    // 十六进制

字符串类型

一个字符串系列,使用单引号(')或双引号(")来表示字符串类型,反引号(`)来定义多行文本和内嵌表达式。

let name: string = "tom";
let age: number = 5;
let words: string = `我叫 ${ name },今年 ${ age }`;

任意类型

声明为 any 的变量可以赋值任意类型的值。

let str:any = 'hello'
str = 12
str = true
str = [1,2,3,4]

任意值是 TypeScript 针对编程时类型不明确的变量使用的一种数据类型,常用于以下三种情况

1、变量的值会动态改变时,比如来自用于的输入,任意类型可以让这些变量跳过编译阶段的类型检查,示例代码如下:

let x: any = 1;    // 数字类型
x = 'I am who I am';    // 字符串类型
x = false;    // 布尔类型

2、改写现有代码时,任意类型允许在编译时可选择地包含或移除类型检查,示例代码如下:

let x: any = 4;
x.ifItExists();    // 正确,ifItExists方法在运行时可能存在,但这里并不会检查
x.toFixed();    // 正确

3、定义存储各种类型数据的数组时,示例代码如下:

let arrayList: any[] = [1, false, 'fine'];
arrayList[1] = 100;

联合类型

如果想要为一个变量赋值多种指定的类型,在声明变量时可以使用 | 为该变量指定多种联合类型。

let numOrStr:number | string = 'hello'
numOrStr = 12
numOrStr = true // 报错

数组类型

声明变量为数组。

// 在元素类型后面加上[]
let arr: number[] = [1, 2];
arr.push('hello') // 报错

// 或者使用数组泛型
let arr: Array<number> = [1, 2];
arr = [1,2,'hello'] // 报错

//混合元素类型
let list: any[] = ['Sherlock', 1887]

元组

元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。

let x: [string, number];
x = ['hello', 1];    // 运行正常
x = [1, 'hello'];    // 报错
x = ['hello', 1, true]; // 报错

console.log(x[0]);    // 输出 hello

枚举

枚举类型用于定义数值集合。

数字枚举:

//数字枚举,枚举会从0开始,依次为元素赋值
enum Color{
    Blue,
    Red,
    Green
}

console.log(Color.Blue); // 输出 0

//使用数字枚举
let a:Color = Color.Blue
console.log(a) // 输出 0

//反向映射
console.log(Color[0]); // 输出字符串类型的 Blue

可以为数字枚举手动赋值:

enum Color{
    Blue = 10,
    Red,
    Green
}

console.log(Color.Blue); // 输出 10
console.log(Color.Red); // 输出 11

//反向映射
console.log(Color[0]); // 输出 undefined

字符串枚举:

enum Color{
    Blue = 'BLUE',
    Red = 'RED',
    Green = 'GREEN'
}

console.log(Color.Blue); // 输出字符串类型的 BLUE
console.log(Color.Red); // 输出字符串类型的 RED

let str = 'RED'
console.log(str === Color.Red); // 输出 true

常量枚举

没有使用常量枚举之前编译的效果:

// index.ts
enum Color{
    Blue = "BLUE",
    Red = 'RED',
    Green = 'GREEN'
}
console.log(Color.Blue);

编译后:

// index.js
var Color;
(function (Color) {
    Color["Blue"] = "BLUE";
    Color["Red"] = "RED";
    Color["Green"] = "GREEN";
})(Color || (Color = {}));
console.log(Color.Blue);

使用常量枚举:

// index.ts
const enum Color{
    Blue = "BLUE",
    Red = 'RED',
    Green = 'GREEN'
}
console.log(Color.Blue);

编译后:

// index.js
console.log("BLUE" /* Blue */);

使用常量枚举可以提升编译的性能,不会将 ts 中的枚举编译成 JavaScrit 代码,而是直接使用常量值,这样就提高了性能。只有常量值才能使用常量枚举。

void

用于表示方法返回值的类型,表示该方法没有返回值,是一个返回值的占位符。

// 当方法没有返回值时,使用void作为占位符
function hello(): void {
    alert("Hello Runoob");
}

声明一个 void 类型的变量没有什么用,只能为变量赋值 undefinednull

let nothing: void = undefined

null

表示对象值缺失。

JavaScriptnull 表示 “什么都没有”。null 是一个只有一个值的特殊类型。表示一个空对象引用。

typeof 检测 null 返回是 object

undefined

用于初始化变量为一个未定义的值。

JavaScript 中, undefined 是一个没有设置值的变量。typeof 一个没有值的变量会返回 undefined

NullUndefined 是其他任何类型(包括 void)的子类型,可以赋值给其它类型,如数字类型,此时,赋值后的类型会变成 nullundefined。而在 TypeScript 中启用严格的空校验( --strictNullChecks )特性,就可以使得 nullundefined 只能被赋值给 void 或本身对应的类型,示例代码如下:

// 启用 --strictNullChecks
let x: number;
x = 1; // 运行正确
x = undefined;    // 运行错误
x = null;    // 运行错误

上面的例子中变量 x 只能是数字类型。如果一个类型可能出现 nullundefined, 可以用 | 来支持多种类型,示例代码如下:

// 启用 --strictNullChecks
let x: number | null | undefined;
x = 1; // 运行正确
x = undefined;    // 运行正确
x = null;    // 运行正确

never

never 是其他类型(包括 nullundefined)的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环),示例代码如下:

let x: never;
let y: number;

// 运行错误,数字类型不能转为 never 类型
x = 123;

// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();

// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();

// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
    throw new Error(message);
}

// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
    while (true) {}
}

注意事项:

  1. TypeScript 中描述类型要用 小写,比如 booleannumberstring 等;
  2. 大写开头的如 BooleanNumberString 代表的是 JavaScript 的构造函数;
//通过 new Number('10') 得到的是一个构造函数,本质是一个对象
let a: Number = new Number('10') // a === 10 为 false

// Number('10') 与 10 都是声明一个数字 10 的方法,本质就是一个数字
let b: number = Number('10') // b === 10 为 true

/*
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。a 是一个对象,它的 __proto__ 属性指向该对象的构造函数的原型对象 Number,所以为 true。b 是一个数字,所以为 false。
*/
a instanceof Number // true
b instanceof Number // false

__proto__ 是非标准属性,你也可以使用 Object.getPrototypeOf() 方法来访问一个对象的原型:

a.__proto__ === Object.getPrototypeOf(a) // true

需要注意的是,TypeScript 一些基本类型,需要记住的是:TypeScript 中描述类型要用 小写。不要滥用 any

3.3、BigInt 类型

概念

bigint 是一种基本数据类型(primitive data type)。

JavaScript 中可以用 Number 表示的最大整数为 2^53 - 1,可以写为 Number.MAX_SAFE_INTEGER。如果超过了这个界限,可以用 BigInt来表示,它可以表示任意大的整数。

语法

在一个整数字面量后加 n 的方式定义一个 BigInt,如:10n 或者调用函数 BigInt()

const theBiggestInt: bigint = 9007199254740991n
const alsoHuge: bigint = BigInt(9007199254740991)
const hugeString: bigint = BigInt("9007199254740991")

theBiggestInt === alsoHuge // true
theBiggestInt === hugeString // true

BigIntNumber 的不同点:

  • BigInt 不能用于 Math 对象中的方法。
  • BigInt 不能和任何 Number 实例混合运算,两者必须转换成同一种类型。
  • BigInt 变量在转换为 Number 变量时可能会丢失精度。

使用 number 类型:

const biggest: number = Number.MAX_SAFE_INTEGER

//最大精度就是这个容器已经完全满了,无论往上加多少都会溢出,所以这两个值是相等的
const biggest1: number = biggest + 1
const biggest2: number = biggest + 2

biggest1 === biggest2 // true 超过精度

使用 BigInt 类型:

const biggest: bigint = BigInt(Number.MAX_SAFE_INTEGER)

//bigint 类型就是用来表示那些已经超出了 number 类型最大值的整数值,也就是这个容器还没满,在此基础上加上两个不同的值,其结果不相等
const biggest1: bigint = biggest + 1n
const biggest2: bigint = biggest + 2n

biggest1 === biggest2 // false

类型信息

使用 typeof 检测类型时,BigInt 对象返回 bigint:

typeof 10n === 'bigint'         // true
typeof BigInt(10) === 'bigint'  // true

typeof 10 === 'number'          // true
typeof Number(10) === 'number'  // true

运算

BigInt 可以正常使用 +-*/**% 符号进行运算:

const previousMaxSafe: bigint = BigInt(Number.MAX_SAFE_INTEGER)  // 9007199254740991n

const maxPlusOne: bigint = previousMaxSafe + 1n                  // 9007199254740992n

const multi: bigint = previousMaxSafe * 2n                       // 18014398509481982n

const subtr: bigint = multi – 10n                                // 18014398509481972n

const mod: bigint = multi % 10n                                  // 2n

const bigN: bigint = 2n ** 54n                                   // 18014398509481984n

当使用 / 操作符时,会向下取整,不会返回小数部分:

const divided: bigint = 5n / 2n                                   // 2n, not 2.5n

比较 与 条件

NumberBigInt 可以进行比较:

0n === 0 // false
0n == 0 // true
1n < 2  // true
2n > 1  // true
2 > 2   // false
2n > 2  // false
2n >= 2 // true

条件判断:

if (0n) {
  console.log('条件成立!');
} else {
  console.log('条件不成立!'); // 输出结果
}

0n || 10n    // 10n
0n && 10n    // 0n
Boolean(0n)  // false
Boolean(10n) // true
!10n         // false
!0n          // true

3.4、Symbol 类型

概念

symbol 是一种基本数据类型(primitive data type)。

Symbol() 函数会返回 symbol 类型的值。每个从 Symbol() 返回的 symbol 值都是唯一的。

语法

Symbol([description])

参数 description:可选的,字符串类型。

使用 Symbol() 创建新的 symbol 类型:

const sym1: symbol = Symbol()
const sym2: symbol = Symbol('foo')
const sym3: symbol = Symbol('foo')

上面的代码创建了三个新的 symbol 类型,但要注意每个从 Symbol() 返回的值都是唯一的:

console.log(sym2 === sym3) // false

每个 Symbol() 方法返回的值都是唯一的,所以,sym2sym3 不相等。

Symbol() 作为构造函数是不完整的:

const sym = new Symbol() // TypeError

这种语法会报错,是因为从 ECMAScript 6 开始围绕原始数据类型创建一个显式包装器对象已不再被支持,但因历史遗留原因, new Boolean()new String() 以及 new Number() 仍可被创建:

const symbol = new Symbol()   // TypeError
const bigint = new BigInt()   // TypeError

const number = new Number()   // OK
const boolean = new Boolean() // OK
const string = new String()   // OK

应用场景

1、当一个对象有较多属性时(往往分布在不同文件中由模块组合而成),很容易将某个属性名覆盖掉,使用 Symbol 值可以避免这一现象,比如 vue-router 中的 name 属性。

// a.js 文件
export const aRouter = {
  path: '/index',
  name: Symbol('index'),
  component: Index
},

// b.js 文件

export const bRouter = {
  path: '/home',
  name: Symbol('index'), // 不重复
  component: Home
},

// routes.js 文件
import { aRouter } from './a.js'
import { bRouter } from './b.js'

const routes = [
  aRouter,
  bRouter
]

两个不同文件使用了同样的 Symbol('index') 作为属性 name 的值,因 symbol 类型的唯一性,就避免了重复定义。

2、模拟类的私有方法。

const permission: symbol = Symbol('permission')

class Auth {
  [permission]() {
    // do something
  }
}

这种情况通过类的实例是无法取到该方法,模拟类的私有方法。

但是,TypeScript 是可以使用 private 关键字的,所以这种方法可以在 JavaScript 中使用。

3、判断是否可以用 for...of 迭代。

if (Symbol.iterator in iterable) {
    for(let n of iterable) {
      console.log(n)
    }
}
  • for...of 循环内部调用的是数据结构的 Symbol.iterator 方法。

  • for...of 只能迭代可枚举属性。

4、Symbol.prototype.description

Symbol([description]) 中可选的字符串即为这个 Symbol 的描述,如果想要获取这个描述:

const sym: symbol = Symbol('imooc')

console.log(sym);               // Symbol(imooc)
console.log(sym.toString());    // Symbol(imooc)
console.log(sym.description);   // imooc

3.5、元组(Tuple

概念

相同类型元素组成成为数组,不同类型元素组成了元组(Tuple)。

语法

声明一个由 stringnumber 构成的元组:

const list: [string, number] = ['Sherlock', 1887]   // ok

const list1: [string, number] = [1887, 'Sherlock']  // error

元组中规定的元素类型顺序必须是完全对照的,而且不能多、不能少,list1 中定义的第一个元素为 string类型,不能赋值为 number类型的数据。

当赋值或访问一个已知索引的元素时,会得到正确的类型:

const list: [string, number] = ['Sherlock', 1887]

//list[0] 是一个字符串类型,拥有 substr() 方法。
list[0].substr(1)  // ok

//list[1] 是一个数字类型,没有 substr() 方法,所以报错
list[1].substr(1)  // Property 'substr' does not exist on type 'number'.

要注意元组的越界问题,虽然可以越界添加元素(不建议),但是不可越界访问:

const list: [string, number] = ['Sherlock', 1887]
//向一个声明了只有两个元素的元组继续添加元素,这种操作虽然可行,但是严重不建议!
list.push('hello world')

//该元组只有两个元素,不可越界访问第三个元素
console.log(list)      // ok [ 'Sherlock', 1887, 'hello world' ]
console.log(list[2])   // Tuple type '[string, number]' of length '2' has no element at index '2'

可选元素类型

元组类型允许在元素类型后缀一个 ? 来说明元素是可选的:

const list: [number, string?, boolean?]
list = [10, 'Sherlock', true]
list = [10, 'Sherlock']
list = [10]

可选元素必须在必选元素的后面,也就是如果一个元素后缀了 ? 号,其后的所有元素都要后缀 ? 号。

元组类型的 Rest 使用

元组可以作为参数传递给函数,函数的 Rest 形参可以定义为元组类型:

declare function rest(...args: [number, string, boolean]): void

等价于:

declare function rest(arg1: number, arg2: string, arg3: boolean): void

还可以这样:

const list: [number, ...string[]] = [10, 'a', 'b', 'c']

const list1: [string, ...number[]] = ['a', 1, 2, 3]

Rest 元素指定了元组类型是无限扩展的,可能有零个或多个具有数组元素类型的额外元素。

关键字 declare 表示声明作用

3.6、枚举(Enum

语法

使用枚举我们可以定义一些带名字的常量。TypeScript 支持数字的和基于字符串的枚举。枚举类型弥补了 JavaScript 的设计不足,很多语言都拥有枚举类型。

当我们需要一组相同主题下的数据时,枚举类型就很有用了。

enum Direction { Up, Down, Left, Right }

enum Months { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }

enum Size { big = '大', medium = '中', small = '小' }

enum Agency { province = 1, city = 2, district = 3 }

代码中通过枚举类型分别声明了:不同的 方向,一年内不同的 月份,一个商品的不同 尺寸属性,经销商的不同 级别,这样的一组常量数据,是在一个相同主题下的不同表示。

数字枚举

声明一个枚举类型,如果没有赋值,它们的值默认为数字类型且从 0 开始累加:

enum Months {
  Jan,
  Feb,
  Mar,
  Apr
}

Months.Jan === 0 // true
Months.Feb === 1 // true
Months.Mar === 2 // true
Months.Apr === 3 // true

现实中月份是从 1 月开始的,那么只需要这样:

// 从第一个数字赋值,往后依次累加
enum Months {
  Jan = 1, //从属性 Jan 被赋值为 1 开始,后续的属性值依次累加
  Feb,
  Mar,
  Apr
}

Months.Jan === 1 // true
Months.Feb === 2 // true
Months.Mar === 3 // true
Months.Apr === 4 // true

字符串枚举

枚举类型的值为字符串类型

enum TokenType {
  ACCESS = 'accessToken',
  REFRESH = 'refreshToken'
}

// 两种不同的取值写法
console.log(TokenType.ACCESS === 'accessToken')        // true
console.log(TokenType['REFRESH'] === 'refreshToken')   // true

枚举的取值,有 TokenType.ACCESSTokenType['ACCESS'] 这两种不同的写法,效果是相同的。

数字类型和字符串类型可以混合使用,但是不建议:

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

计算常量成员

枚举类型的值可以是一个简单的计算表达式:

enum Calculate {
  a,
  b,
  expired = 60 * 60 * 24,
  length = 'imooc'.length,
  plus = 'hello ' + 'world'
}

console.log(Calculate.expired)   // 86400
console.log(Calculate.length)    // 5
console.log(Calculate.plus)      // hello world
  • 计算结果必须为常量。

  • 计算项必须放在最后。

反向映射

所谓的反向映射就是指枚举的取值,不但可以正向的 Months.Jan 这样取值,也可以反向的 Months[1] 这样取值。

enum Months {
  Jan = 1,
  Feb,
  Mar,
  Apr
}

将上面的代码进行编译,查看编译后的 JavaScript 代码:

'use strict'
var Months;
(function (Months) {
  Months[Months['Jan'] = 1] = 'Jan'
  Months[Months['Feb'] = 2] = 'Feb'
  Months[Months['Mar'] = 3] = 'Mar'
  Months[Months['Apr'] = 4] = 'Apr'
})(Months || (Months = {}))

通过查看编译后的代码,可以得出:

console.log(Months.Mar === 3) // true

// 那么反过来能取到 Months[3] 的值吗?
console.log(Months[3])  // 'Mar'

// 所以
console.log(Months.Mar === 3)     // true
console.log(Months[3] === 'Mar')  // true
  • 字符串枚举成员不会生成反向映射。

  • 枚举类型被编译成一个对象,它包含了正向映射( name -> value)和反向映射( value -> name)。

const 枚举

在枚举上使用 const 修饰符:

enum Months {
  Jan = 1,
  Feb,
  Mar,
  Apr
}

const month = Months.Mar

查看一下编译后的内容:

'use strict'
const month = 3 /* Mar */

发现枚举类型应该编译出的对象没有了,只剩下 month 常量。这就是使用 const 关键字声明枚举的作用。因为变量 month 已经使用过枚举类型,在编译阶段 TypeScript 就将枚举类型抹去,这也是性能提升的一种方案。

枚举合并

分开声明名称相同的枚举类型,会自动合并:

enum Months {
  Jan = 1,
  Feb,
  Mar,
  Apr
}

enum Months {
  May = 5,
  Jun
}

console.log(Months.Apr) // 4
console.log(Months.Jun) // 6

编译后的 JavaScript 代码:

'use strict'
var Months;
(function (Months) {
  Months[Months['Jan'] = 1] = 'Jan'
  Months[Months['Feb'] = 2] = 'Feb'
  Months[Months['Mar'] = 3] = 'Mar'
  Months[Months['Apr'] = 4] = 'Apr'
})(Months || (Months = {}));
(function (Months) {
  Months[Months['May'] = 5] = 'May'
  Months[Months['Jun'] = 6] = 'Jun'
})(Months || (Months = {}))

console.log(Months.Apr) // 4
console.log(Months.Jun) // 6

3.7、Never 类型

概念

never 类型表示那些永不存在的值的类型。

never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never

应用场景

一个抛出异常的函数表达式,其函数返回值类型为 never

function error(message:string): never {
  throw new Error(message)
}

同样的,不会有返回值的函数表达式,其函数返回值类型也为 never

// 推断的返回值类型为 never
function fail(): never {
    return error("Something failed")
}

不能取得值的地方:

interface Foo {
  type: 'foo'
}

interface Bar {
  type: 'bar'
}

type All = Foo | Bar

function handleValue(val: All) {
  switch (val.type) {
    case 'foo':
      break
    case 'bar':
      break
    default:
      // 此处不能取值
      const exhaustiveCheck: never = val
      break
  }
}

3.8、unknown 类型

any无需事先执行任何类型的检查:

let value: any

value = true             // OK
value = 10               // OK
value = "Hello World"    // OK
value = []               // OK
value = {}               // OK
value = Math.random      // OK
value = null             // OK
value = undefined        // OK
value = new TypeError()  // OK
value = Symbol('name')   // OK

value.foo.bar            // OK
value.trim()             // OK
value()                  // OK
new value()              // OK
value[0][1]              // OK

在许多情况下,这太宽松了。如果使用了 unknown 类型:

let value: unknown

value = true             // OK
value = 10               // OK
value = "Hello World"    // OK
value = []               // OK
value = {}               // OK
value = Math.random      // OK
value = null             // OK
value = undefined        // OK
value = new TypeError()  // OK
value = Symbol('name')   // OK

所有对该 value 变量的分配都被认为是类型正确的。

可以尝试:

let value: unknown

let value1: unknown = value   // OK
let value2: any = value       // OK

let value3: boolean = value   // Error
let value4: number = value    // Error
let value5: string = value    // Error
let value6: object = value    // Error
let value7: any[] = value     // Error

可以看到,该 unknown 类型只能分配给 any 类型和 unknown 类型本身。

继续尝试:

let value: unknown

value.foo.bar  // Error
value.trim()   // Error
value()        // Error
new value()    // Error
value[0][1]    // Error

unknown 类型在被确定为某个类型之前,不能被进行诸如函数执行、实例化等操作,一定程度上对类型进行了保护。

在那些将取得任意值,但不知道具体类型的地方使用 unknown,而非 any

4、接口(Interface

4.1、接口的概念

接口是对 JavaScript 本身的随意性进行约束,通过定义一个接口,约定了变量、类、函数等应该按照什么样的格式进行声明,实现多人合作的一致性。TypeScript 编译器依赖接口用于类型检查,最终编译为 JavaScript 后,接口将会被移除。

// 语法格式
interface DemoInterface {

}

4.2、应用场景

在声明一个对象函数或者时,先定义接口,确保其数据结构的一致性。

在多人协作时,定义接口尤为重要。

4.3、接口的好处

过去我们写 JavaScript 定义一个函数:

function getClothesInfo(clothes) {
  console.log(clothes.price)
}

let myClothes = {
  color: 'black', 
  size: 'XL', 
  price: 98 
}
getClothesInfo(myClothes)

之前我们写 JavaScript 这样是很正常的,但同时你可能会遇到下面这些问题:

getClothesInfo() // Uncaught TypeError: Cannot read property 'price' of undefined
getClothesInfo({ color: 'black' }) // undefined

相信原因你也知道,JavaScript弱类型 语言,并不会对传入的参数进行任何检测,错误在运行时才被发现。那么通过定义 接口,在编译阶段甚至开发阶段就避免掉这类错误,接口将检查类型是否和某种结构做匹配。

下面通过接口的方式重写之前的例子:

interface Clothes {
  color: string;
  size: string;
  price: number;
}

function getClothesInfo(clothes: Clothes) {
  console.log(clothes.price)
}

let myClothes: Clothes = { 
  color: 'black', 
  size: 'XL', 
  price: 98 
}

getClothesInfo(myClothes)

代码中,定义了一个接口 Clothes,在传入的变量 clothes 中,它的类型为 Clothes。这样,就约束了这个传入对象的 外形 与接口定义一致。只要传入的对象满足上面的类型约束,那么它就是被允许的。

  • 定义接口要 首字母大写。
  • 只需要关注值的 外形,并不像其他语言一样,定义接口是为了实现。
  • 如果没有特殊声明,定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致。

4.4、接口的属性

可选属性

接口中的属性不全是必需的。可选属性的含义是该属性在被变量定义时可以不存在。

// 语法
interface Clothes {
  color?: string;
  size: string;
  price: number;
}

// 这里可以不定义属性 color
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ? 符号。这时,仍不允许添加未定义的属性,如果引用了不存在的属性时 TS 将直接捕获错误。

只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。你可以在属性名前用 readonly 来指定只读属性,比如价格是不能被修改的:

// 语法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
}

// 创建的时候给 price 赋值
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

// 不可修改
myClothes.price = 100
// error TS2540: Cannot assign to 'price' because it is a constant or a read-only property

TypeScript 可以通过 ReadonlyArray<T> 设置数组为只读,那么它的所有写方法都会失效。

let arr: ReadonlyArray<number> = [1,2,3,4,5];
arr[0] = 6; // Index signature in type 'readonly number[]' only permits reading

最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。做为 变量 使用的话用 const,若做为 属性 则使用 readonly

任意属性

有时候我们希望接口允许有任意的属性,语法是用 [] 将属性包裹起来:

// 语法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
  [propName: string]: any;
}

// 任意属性 activity
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98,
  activity: 'coupon'
}

这里的接口 Clothes 可以有任意数量的属性,并且只要它们不是 color sizeprice,那么就无所谓它们的类型是什么。

案例:

使用 axios 库发起 HTTP 传输的时候,可以写入一个自定义的属性,就是因为源码中定义了一个任意属性:

this.$axios({
  method: 'put',
  url: '/cms/user',
  data: {
    nickname: this.nickname,
  },
  showBackend: true,
})

4.5、函数类型

除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有 参数列表返回值类型 的函数定义。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean {
  return source.search(subString) > -1;
}

对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配。你可以改变函数的参数名,只要保证函数参数的位置不变。函数的参数会被逐个进行检查:

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
// source => src, subString => sub
mySearch = function(src: string, sub: string): boolean {
  return src.search(sub) > -1;
}

如果你不想指定类型,TypeScript 的类型系统会推断出参数类型,因为函数直接赋值给了 SearchFunc 类型变量。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(src, sub) {
  let result = src.search(sub);
  return result > -1;
}

如果接口中的函数类型带有函数名,下面两种书写方式是等价的:

interface Calculate {
  add(x: number, y: number): number
  multiply: (x: number, y: number) => number
}

4.6、可索引类型

代码示例:

// 正常的js代码
let arr = [1, 2, 3, 4, 5]
let obj = {
  brand: 'imooc',
  type: 'education'
}

arr[0]
obj['brand']

定义可索引类型接口:

interface ScenicInterface {
  [index: number]: string
}

let arr: ScenicInterface = ['西湖', '华山', '故宫']
let favorite: string = arr[0]

示例中索引签名是 number类型,返回值是字符串类型。

另外还有一种索引签名是 字符串类型。我们可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。通过下面的例子理解这句话:

// 正确
interface Foo {
  [index: string]: number;
  x: number;
  y: number;
}

// 错误
interface Bar {
  [index: string]: number;
  x: number;
  y: string; // Error: y 属性必须为 number 类型
}

第 12 行,语法错误是因为当使用 number 来索引时,JavaScript 会将它转换成 string 然后再去索引对象。也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

4.7、类类型

我们希望类的实现必须遵循接口定义,那么可以使用 implements 关键字来确保兼容性。

这种类型的接口在传统面向对象语言中最为常见,比如 java 中接口就是这种类类型的接口。这种接口与抽象类比较相似,但是接口只能含有抽象方法和成员属性,实现类中必须实现接口中所有的抽象方法和成员属性。

interface AnimalInterface {
  name: string;
}

class Dog implements AnimalInterface {
  name: string;
  
  constructor(name: string){
    this.name = name
  }
}

你也可以在接口中描述一个方法,在类里实现它:

interface AnimalInterface {
  name: string

  eat(m: number): string
}

class Dog implements AnimalInterface {
  name: string;

  constructor(name: string){
    this.name = name
  }

  eat(m: number) {
    return `${this.name}吃肉${m}分钟`
  }
}

接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。

4.8、继承接口

和类一样,接口也可以通过关键字 extents 相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square = {} as Square;
// 继承了 Shape 的属性
square.color = "blue";
square.sideLength = 10;

一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

4.9、混合类型

接口可以描述函数、对象的方法或者对象的属性。

有时希望一个对象同时具有上面提到多种类型,比如一个对象可以当做函数使用,同时又具有属性和方法。

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = function (start: number) { } as Counter;
  counter.interval = 123;
  counter.reset = function () { };
  return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

上面代码中,第 1 行,声明一个接口,如果只有 (start: number): string 一个成员,那么这个接口就是函数接口,同时还具有其他两个成员,可以用来描述对象的属性和方法,这样就构成了一个混合接口。

第 7 行,创建一个 getCounter() 函数,它的返回值是 Counter 类型的。

let counter = function (start: number) { } as Counter;

第 8 行,通过类型断言,将函数对象转换为 Counter 类型,转换后的对象不但实现了函数接口的描述,使之成为一个函数,还具有 interval 属性和 reset() 方法。断言成功的条件是,两个数据类型只要有一方可以赋值给另一方,这里函数类型数据不能赋值给接口类型的变量,因为它不具有 interval 属性和 reset() 方法。

创建 React 项目

创建项目

执行下面的命令新建一个名字为 demoReact 项目:

npx create-react-app demo --template typescript

根据 typescript 官网文档的说明,还可以使用下面的命令:

npx create-react-app demo --scripts-version=react-scripts-ts

react-script-ts是一个在采用了标准的 create-react-app 项目流程 的基础上,混合了 TypeScript 的功能的集合。

我想,原来是采用的第二种方式,后来就整理为了第一种方式,这里采用第一种方式。

创建的项目文件结构:

demo02-ts-template/
  |─   node_modules/
  |─   public/
  |      └─ favicon.ico
  |      └─ index.html
  |      └─ manifest.json
  |      └─ ...
  |─   src/
  |      └─ ...
  |─   .gitignore
  |─   package.json
  |─   package-lock.json
  |─   README.md
  └─   tsconfig.json

执行:

npm start

运行项目,默认服务将运行在 localhost:3000

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-27 16:07:20  更:2021-07-27 16:08:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 14:35:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码