简介

TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统

参考资料

tsconfig.json 文件

  • tsc --init命令可生成 tsconfig.json 文件

总览

  • 具体使用可见查官网配置简介
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
{
"compilerOptions": {
"incremental": true,
// TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
"tsBuildInfoFile": "./buildFile",
// 增量编译文件的存储位置
"diagnostics": true,
// 打印诊断信息
"target": "ES5",
// 目标语言的版本
"module": "CommonJS",
// 生成代码的模板标准
"outFile": "./app.js",
// 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": [
"DOM",
"ES2015",
"ScriptHost",
"ES2019.Array"
],
// TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true,
// 允许编译器编译JS,JSX文件
"checkJs": true,
// 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist",
// 指定输出目录
"rootDir": "./",
// 指定输出文件目录(用于输出),用于控制输出目录结构
"declaration": true,
// 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file",
// 指定生成声明文件存放目录
"emitDeclarationOnly": true,
// 只生成声明文件,而不会生成js文件
"sourceMap": true,
// 生成目标文件的sourceMap文件
"inlineSourceMap": true,
// 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true,
// 为声明文件生成sourceMap
"typeRoots": [],
// 声明文件目录,默认时node_modules/@types
"types": [],
// 加载的声明文件包
"removeComments": true,
// 删除注释
"noEmit": true,
// 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true,
// 发送错误时不输出任何文件
"noEmitHelpers": true,
// 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true,
// 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true,
// 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true,
// 开启所有严格的类型检查
"alwaysStrict": true,
// 在代码中注入'use strict'
"noImplicitAny": true,
// 不允许隐式的any类型
"strictNullChecks": true,
// 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true,
// 不允许函数参数双向协变
"strictPropertyInitialization": true,
// 类的实例属性必须初始化
"strictBindCallApply": true,
// 严格的bind/call/apply检查
"noImplicitThis": true,
// 不允许this有隐式的any类型
"noUnusedLocals": true,
// 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true,
// 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true,
// 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true,
//每个分支都会有返回值
"esModuleInterop": true,
// 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true,
// 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node",
// 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./",
// 解析非相对模块的基地址,默认是当前目录
"paths": {
// 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": [
"node_modules/jquery/dist/jquery.min.js"
]
},
"rootDirs": [
"src",
"out"
],
// 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true,
// 打印输出文件
"listFiles": true
// 打印编译的文件(包括引用的声明文件)
},
// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
"include": [
"src/**/*"
],
// 指定一个排除列表(include的反向操作)
"exclude": [
"demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)
"files": [
"demo.ts"
]
}

常用配置

include

  • 编译默认是编译当前目录下所有的ts文件,也可用 include 指定需要编译的文件
  • 如下所示,指定只编译 index.ts index3.ts 两个文件
1
2
3
4
5
6
{
"include": [
"./src/index.ts",
"./src/index3.ts"
]
}

include

exclude

  • 和 include 相反,指定编译需要排除的文件
  • 如下所示即表示,编译时排除 ./src/index3.ts 这个文件
1
2
3
4
5
{
"exclude": [
"./src/index3.ts"
]
}

target

  • 指定编译 js 的版本例如 es5 es6 es2016
  • 一般采用 es6 的较多,如果为了兼容低版本的浏览器可以指定 es5 或者更低
  • 该属性时写在 compilerOptions 内的
1
2
3
4
5
6
7
8
{
"exclude": [
"./src/index3.ts"
],
"compilerOptions": {
"target": "es6"
}
}

target

allowsJS

  • 是否允许编译 js 文件
  • 如果项目中有 js 和 ts 就需要设置该属性开启一起编译(不过一个项目最好还是固定一种)
1
2
3
4
5
6
7
8
9
{
"include": [
"./src/index2.ts"
],
"compilerOptions": {
"target": "es6",
"allowJs": true
}
}

removeComments

  • 是否在编译过程中删除文件中的注释

rootDir

  • 编译文件的目录

outDir

  • 输出的目录
  • 打包后的目录 一般默认为 ./dist

sourceMap

  • 代码源文件

strict

  • 严格模式
  • 布尔值,开启后就不能滥用类型

module

  • 默认common.js 可选es6模式 amd umd 等
  • 看上去定义定义方法 导包的关键字不一样了

namespace

简介

  • 在我们的工作中无法避免 全局变量 造成的污染,在 JS 中我们一般是通过模块的方式进行导出进行解决
  • 全局变量污染: 在两个不同的文件中定义了相同变量名的全局变量会造成重复,通过 export进行导出,当成一个模块 的方式进行解决
1
export let num: number = 100
  • 在 TypeScript 中为我们提供了 namespace 避免这个问题的出现
  • 通过 namespace 关键字定义,通过 export 暴露
  • 内部模块,主要用于组织代码,避免命名冲突。
  • 命名空间内的类默认私有

基本使用

  • namespace 定义,export 导出
  • 使用时用 命名空间名.导出的对象或者方法
1
2
3
4
5
6
7
8
9
// 命名空间
namespace base {
export let num: number = 1888
export const print = (args: number) => {
console.log(args)
}
}

base.print(base.num)

嵌套命名空间

  • 里面加一层命名空间,命名空间名前面加上 export 关键字
  • 支持多层或者多个嵌套
  • 使用时注意嵌套关系
1
2
3
4
5
6
7
8
9
10
11
// 嵌套命名空间
namespace basePro {
export namespace base {
export let str: string = "ada"
export const say = (str: string) => {
console.log(`我是 ${str}`)
}
}
}

basePro.base.say(basePro.base.str)

抽离命令空间

1
2
3
4
// 抽离命名空间
export namespace abs {
export let num: number = 10
}
1
2
3
import {abs} from "./abs"

console.log(abs.num);

抽离命名空间

简化命名空间

  • 在上面的嵌套命名空间中,使用一个里层的命名空间往往需要带上前面几层例如 basePro.base.say(basePro.base.str)
  • 我们可以通过 import 的方式进行导入简化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 嵌套命名空间
namespace basePro {
export namespace base {
export let str: string = "ada"
export const say = (str: string) => {
console.log(`我是 ${str}`)
}
}
}
// 普通用法
basePro.base.say(basePro.base.str)

// 简化命名空间(有点类之前的别名)
import base1 = basePro.base;
import str2 = basePro.base.str;

console.log(str2)
base1.say(str2)

简化命名空间

合并命名空间

  • 重名的命名空间会自动合并
1
2
3
4
5
6
7
8
9
10
11
namespace User {
export let name: string = "ada"
}

namespace User {
export let age: number = 20
}
// 两个命名空间名称相同时会自动合并
console.log(User)
console.log(User.name)
console.log(User.age)

合并命名空间

三斜线指令

  • 三斜线指令是包含单个 XML 标签的单行注释。 注释的内容会做为编译器指令使用
  • 三斜线指令可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义
  • /// <reference path="..." />指令是三斜线指令中最常见的一种,它用于声明文件间的依赖
  • 三斜线引用告诉编译器在编译过程中要引入的额外的文件,你也可以把它理解能import,它可以告诉编译器在编译过程中要引入的额外的文件
1
2
3
namespace A {
export const fn = () => 'a'
}
1
2
3
namespace A {
export const fn2 = () => 'b'
}
1
2
3
4
///<reference path="./index2.ts" />
///<reference path="./index3.ts" />

console.log(A);

  • 声明文件引入
  • yarn add @types/node -D
  • /// <reference types="node" /> 就会找到 @types/node/index.d.ts
1
/// <reference types="node" />

声明文件引入

声明文件 d.ts

  • 声明文件 declare 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
1
2
3
4
5
6
7
declare var 声明全局变量
declare function 声明全局方法
declare class 声明全局类
declare enum 声明全局枚举类型
declare namespace 声明(含有子属性的)全局对象
interface 和 type 声明全局类型
/// <reference /> 三斜线指令
  • 假设我们有两个依赖 axios express
1
yarn add axios express -S
  • 如果导入报错提示安装 @type/xxxxx 那么直接进行安装依赖即可(原因的一些老的依赖没有声明文件 index.d.ts 基本上通过 @type 进行安装都行(前提是存在),微软在 npm 上维护了大量的 )

Mixins混入

对象混入

  • 可以使用 ES6 的 Object.assign 合并多个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Object.assign 对象混入
enum GENDER {
WOMAN, MAN, OTHER
}

interface Name {
name: string
}

interface Age {
age: number
}

interface Gender {
gender: GENDER
}

let p1: Name = {name: "ada"}
let p2: Age = {age: 66}
let p3: Gender = {gender: GENDER.MAN}
// 此时的 User 会被推断成 Name & Age & Gender 交叉类型
const User = Object.assign(p1, p2, p3)
console.log(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
51
52
53
54
55
56
57
58
59
60
61
// 类的混入
class A {
type: boolean = false;

constructor() {
}

changeType() {
this.type = !this.type
}
}


class B {
name: string = '张三';

constructor() {
}

getName(): string {
return this.name;
}
}

// 下面创建一个 C 类,结合了这两个 mixins,没使用extends而是使用implements。 把类当成了接口
class C implements A, B {
type: boolean
name: string;

constructor(type: boolean, name: string) {
this.name = name;
this.type = type
}

changeType(): void {
}

getName(): string {
return "";
}
}

// 最后,创建这个帮助函数,帮我们做混入操作 curClazz 目标类-->C itemClazz 混入类数组 [A,B]
function Mixins(curCls: any, itemCls: any[]) {
itemCls.forEach(item => {
console.log(item)
// Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性
Object.getOwnPropertyNames(item.prototype).forEach(name => {
console.log(name)
curCls.prototype[name] = item.prototype[name]
})
})
}

// 把 A B 两个类的方法都混入编入到 C 中
Mixins(C, [A, B])

let c1 = new C(false, "ada");
c1.changeType()
console.log(c1.type);
console.log(c1.getName());

类混入

Decorator

装饰器简介

  • 装饰器是一种特殊类型的声明,它能够被附加到 类声明 方法 访问符 属性参数上。
  • Decorator 装饰器是一项实验性特性,在未来的版本中可能会发生改变(我个人的理解就是后端的注解)
  • 它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能
  • 若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用编译器选项
1
2
3
4
5
{
"compilerOptions": {
"experimentalDecorators": true
}
}

无参类装饰器

  • 定义类装饰器使用 ClassDecorator 记得要区分作用域,类装饰器就在类上用
  • target: Function 表示使用该装饰器的对象
  • 使用时通过 @装饰器名
  • 注意使用 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
// ClassDecorator 作用在类上装饰器
const watcher: ClassDecorator = (target: Function) => {
console.log(target)
// 给传入的类追加一个泛型方法 getName
target.prototype.getInput = <T>(name: T): T => {
return name;
}
}

// 在类上通过 @ 符号 + 装饰器名称进行使用
@watcher
class User {
}

let user = new User();
// 由于 User 类没有直接声明或者继承 getInput 方法编译会报错,所以需要临时断言成 any 类型
let u1 = (<any>user).getInput("人类")
console.log(u1)

@watcher
class Phone {
}

let phone = new Phone();
// 同样这里也要使用 any 临时断言
let p1 = (<any>phone).getInput(1888)
console.log(p1)

无参类装饰器

带参类装饰器

  • 装饰器工厂,其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数
  • 同样使用 ClassDecorator 来表示类装饰器
  • 装饰器参数可定义多个,这里只定义了一个 type,约束为 string
  • 在类上使用该装饰器时,需要传一个 string 类型的值给装饰器
  • 装饰器的参数和内部的 sum 函数的参数是独立的,内部的 sum 函数是在使用了该装饰器的类对象调用时的传参
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
// 带参数的类装饰器,可以定义多个参数,目前装饰器只定义了一个 type
const sum = (type: string): ClassDecorator => {
// 高阶函数通过 @sum 在类上,返回一个 sum 函数,函数需要两个参数 a b 函数的执行逻辑由 type 决定
return (target: Function) => {
target.prototype.sum = (a: number, b: number): number => {
if (type === "add") {
return add(a, b)
} else if (type === "minus") {
return minus(a, b)
}
}
}

function add(a, b) {
return a + b
}

function minus(a, b) {
return a - b
}
}

@sum("add")
class Add {

}

@sum("minus")
class Minus {

}

let add = new Add();
console.log((<any>add).sum(1, 2));
let minus = new Minus();
console.log((<any>minus).sum(1, 2));

带参类装饰器

组合式装饰器

  • 说人话就是可以使用多个装饰器
  • 没什么特殊的,就是在使用的地方引用多个已定义的装饰器就行
  • 引用的时候可以写在一行或者换行书写
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
// 组合装饰器,先定义两个装饰器
let Age: ClassDecorator = (target: Function) => {
target.prototype.age = 18
}

let Amount: ClassDecorator = (target: Function) => {
target.prototype.amount = 3000
}

// 组合装饰器的使用1
@Age @Amount
class User2 {

}

// 组合装饰器使用2
@Age
@Amount
class User3 {

}

console.log((<any>new User2()).age);
console.log((<any>new User2()).amount);
console.log((<any>new User3()).age);
console.log((<any>new User3()).amount);

装饰器组合

方法装饰器

  • 该装饰器作用在方法上,通过关键字 MethodDecorator
  • 返回的信息也是三部分,最后的 value writable enumerable configurable 是一些附加信息

方法装饰器

方法的形参装饰器

  • 作用在方法的形参上,通过关键字 ParameterDecorator 定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 作用在方法的形参上
const parm1: ParameterDecorator = (...args) => {
console.log(args);
}


class Pa1 {
name: string

constructor() {
}

// 返回 [ { getName: [Function (anonymous)] }, 'getName', 2 ]
// 第二个值表示方法的名称,第三个值 2 表示使用修饰器参数的 index 为 2 (从 0 开始)
getName(name: string, age: number, @parm1 amount: number) {
}
}

方法的形参装饰器

属性装饰器

  • 该装饰器作用在属性上,通过关键字 PropertyDecorator 定义
  • 控制台会输出三个属性分别是 target 参数名 还有一个 undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const prop1: PropertyDecorator = (...args) => {
console.log(args);
}

const prop2: PropertyDecorator = (target, ...args) => {
console.log(target, args);
}

class P1 {
@prop1
age: 18
amount: 3000
}

class P2 {
@prop2
age: 18
amount: 3000
}

属性装饰器