TS基础知识

2022/3/1 TypeScript

TypeScript 手书、教程整理。

# TS与JS

TS是JS的超集,包含了最新的JS的功能,支持js不支持的强类型、泛型、模块、接口,需要编译成JS后在浏览器中使用。

编译后生成js文件,使用方法就和普通的js一样了。

所以,常见的项目工作流为:TS 编译生成 -> JS 打包 -> main.js 引用执行。

TS 包含所有最新的ES版本,从ES3 ~ ES2021(ES12)

# 使用TS

# 常用命令

# 安装命令
sudo npm install typescript -g
# 查看版本
tsc -v
# 生成tsconfig.json
tsc --init
# 编译某个文件
tsc file.name
# 编译ts,按照tsconfig.json配置,编译文件夹
tsc
# 编译并监听变化,动态更新编译结果
tsc --watch
1
2
3
4
5
6
7
8
9
10
11
12

其他参数 可以看handlebook中对tsconfig.json的介绍。

# 配置文件

官方初始化生成的ts配置表翻译对比 version = 4.6.2,保留原文的为翻译可能不正确

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Projects */
    // "incremental": true,                              /* 增量编译 */
    // "composite": true,                                /* 组合模式 */
    // "tsBuildInfoFile": "./",                          /* 增量编译文件存储位置 */
    // "disableSourceOfProjectReferenceRedirect": true,  /* 组合模式下,不用源文件,使用 .d.ts 文件 */
    // "disableSolutionSearching": true,                 /* 热更新时不完整编译整个项目 Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* 热更新时减少更新频率 Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* 目标js版本,默认ES3 */
    // "lib": [],                                        /* 编译中需要饮用的库列表;target=ES5默认为DOM,ES5,ScriptHost,target=ES6默认为target ES6:DOM,ES6,DOM.Iterable,ScriptHost */
    // "jsx": "preserve",                                /* 支持的jsx模式  preserve为保留jsx文件 react为使用React.createElement */
    // "experimentalDecorators": true,                   /* 启用试验性的ES装饰器 */
    // "emitDecoratorMetadata": true,                    /* 支持元数据反射(ES7) */
    // "jsxFactory": "",                                 /* 配置jsx工厂函数使用React.createElement 或 h */
    // "jsxFragmentFactory": "",                         /* 配置jsx片段使用React.Fragment 或 Fragment */
    // "jsxImportSource": "",                            /* 配置导入的jsx工厂函数的模块 `jsx: react-jsx*` */
    // "reactNamespace": "",                             /* 使用react、jsx时,配置createElement的调用对象  */
    // "noLib": true,                                    /* 不使用默认的库文件 lib.d.ts. */
    // "useDefineForClassFields": true,                  /* 在class中声明的字段,使用 Object.defineProperty 来定义 */

    /* Modules */
    "module": "commonjs",                                /* 使用commonjs模式引入模块 */
    // "rootDir": "./",                                  /* 指定输入文件目录 */
    // "moduleResolution": "node",                       /* 配置模块解析侧策略 */
    // "baseUrl": "./",                                  /* 配置非相对路径引入模块时的默认根目录 */
    // "paths": {},                                      /* 模块-模块路径 映射 */
    // "rootDirs": [],                                   /* 配置源文件有多路径的情况 */
    // "typeRoots": [],                                  /* 声明文件目录 默认是 ./node_modules/@types. */
    // "types": [],                                      /* 声明文件包 */
    // "allowUmdGlobalAccess": true,                     /* 允许在模块中访问UMD全局变量 */
    // "resolveJsonModule": true,                        /* 允许导入.json文件 */
    // "noResolve": true,                                /* 不编译 import, require or <reference> 导入的文件 */

    /* JavaScript Support */
    // "allowJs": true,                                  /* 允许编译JS文件 */
    // "checkJs": true,                                  /* 编译js文件时,允许抛出错误 */
    // "maxNodeModuleJsDepth": 1,                        /* nodemoudule 最大搜索深度 */

    /* Emit */
    // "declaration": true,                              /* 生成 .d.ts 文件 */
    // "declarationMap": true,                           /* 给 .d.ts 文件 增加 sourcemap */
    // "emitDeclarationOnly": true,                      /* 只生成 .d.ts 文件 不生成 js 文件 */
    // "sourceMap": true,                                /* 生成.map文件 */
    // "outFile": "./",                                  /* 输出的文件名 */
    "outDir": "./dist",                               /* 输出的文件目录 */
    "removeComments": true,                           /* 去掉注释 */
    // "noEmit": true,                                   /* 不生成输出文件 */
    // "importHelpers": true,                            /* 全局导入tslib */
    // "importsNotUsedAsValues": "remove",               /* 配置没有使用的导入语句应该如何处理  默认 remove 丢弃, preserve 保留, error 保留并允许抛出错误 */
    // "downlevelIteration": true,                       /* 遍历器 iteration 的降级实现 (es3、es5) */
    // "sourceRoot": "",                                 /* 给ts文件加源文件路径 sourcemap */
    // "mapRoot": "",                                    /* 指定上面生成的sourcemap路径 */
    // "inlineSourceMap": true,                          /* 合并生成的souecmap */
    // "inlineSources": true,                            /* 将代码与sourcemao生成到一个文件里 */
    // "emitBOM": true,                                  /* 在生成的文件开头加BOM头(UTF-8 Byte Order Mark) */
    // "newLine": "crlf",                                /* 指定行结束符是crlf 还是 lf */
    // "stripInternal": true,                            /* 不对具有 /** @internal */ JSDoc注解的代码生成代码。 */
    // "noEmitHelpers": true,                            /* 不在输出文件中生成用户自定义的帮助函数代码 */
    // "noEmitOnError": true,                            /* 报错时不输出 */
    // "preserveConstEnums": true,                       /* 保留 const 和 enum 声明 */
    // "declarationDir": "./",                           /* 声明文件的输出路径 */
    // "preserveValueImports": true,                     /* 是否保留没有使用的变量 */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* 是否保证模块可以单独使用 */
    // "allowSyntheticDefaultImports": true,             /* 是否允许 import from 没有 export default 的模块 */
    "esModuleInterop": true,                             /* 允许 export = XX */
    // "preserveSymlinks": true,                         /* 不把符号链接解析为其真实路径 */
    "forceConsistentCasingInFileNames": true,            /* 禁止对同一个文件的不一致的引用 */

    /* Type Checking */
    "strict": true,                                      /* 启用严格的检查选项 */
    // "noImplicitAny": true,                            /* 禁止表达式和声明上有 隐含的 any */
    // "strictNullChecks": true,                         /* null和undefined不包含在其他类型中,除了 void-undefined  */
    // "strictFunctionTypes": true,                      /* 禁止函数参数双向协变 */
    // "strictBindCallApply": true,                      /* 严格检查bind、apply、call的参数是否与目标函数匹配 */
    // "strictPropertyInitialization": true,             /* 类的实例属性必须初始化 */
    // "noImplicitThis": true,                           /* 禁止this表达式的值为any */
    // "useUnknownInCatchVariables": true,               /* Type catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* 自动生成 use strict 语句 */
    // "noUnusedLocals": true,                           /* 禁止有未使用的局部变量 */
    // "noUnusedParameters": true,                       /* 禁止有未使用的参数 */
    // "exactOptionalPropertyTypes": true,               /* 明确定义的类型不支持赋值为 undefined */
    // "noImplicitReturns": true,                        /* 禁止函数没有 return */
    // "noFallthroughCasesInSwitch": true,               /* 禁止switch中不合理使用break */
    // "noUncheckedIndexedAccess": true,                 /* 使用索引访问属性时,该类型可能(没有定义类型等情况)被加上索引 */
    // "noImplicitOverride": true,                       /* 使用 override 时,原型链上一定要有重名参数 */
    // "noPropertyAccessFromIndexSignature": true,       /* 允许一个类型有一个字符串索引的时候,使用点式调用 */
    // "allowUnusedLabels": true,                        /* 允许未使用的标签错误 */
    // "allowUnreachableCode": true,                     /* 允许执行不到的代码错误 */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* 忽略库的声明文件的类型检查 */
    "skipLibCheck": true                                 /* 忽略所有的声明文件( *.d.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
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

注释:

# 类型

# 基础类型与类型注释

TS只在编译的时候判断类型,在生成的js代码中,是不包含类型判断的。

  1. Boolen
  2. Number
  3. String
  4. Array 两种写法
  5. tuple 元组,类似array,长度固定
  6. Symbol 需要编译为ES6
  7. bigint 需要编译为ES2020
  8. Enum 枚举 类似object,且可以根据value取key
  9. Any 任意值 会导致ts不去判断类型,js执行可能会报错
  10. Void 空值 表示没有任何类型,例如函数没有返回 或者 赋值 undefined或null
  11. Null
  12. Undefined
  13. Never 永不存在的值,例如函数中永远没有终点的情况
  14. // 类型注释【:】
    let boolenVal: boolen = true // var boolenVal = true
    let num: number = 123 // var num = 123
    let str: string = 'abc' // var str = 'abc'
    
    // 泛型写法
    let list1: Array<number> = [1,2,3] // let list1 = [1, 2, 3];
    // 默认写法
    let list2: number[] = [1,2,3]
    
    // 元组写法:数组长度不变
    let list: [number, number, number] = [1,2,3] // var list = [1, 2, 3];
    
    let symbolVal = Symbol(); // 输出的结果和ts写法一致
    let obj = {
      [symbolVal]: "abc"
    }
    
    enum Color {Red = 2, Green, Blue} // 如果值为number 可以自动递增赋值;其他情况必须全部手动赋值。
    /*
    (function (Color) {
      Color[Color["Red"] = 2] = "Red";
      Color[Color["Green"] = 3] = "Green";
      Color[Color["Blue"] = 4] = "Blue";
    })(Color || (Color = {}));
    */
    let green_color: Color = Color.Green // let green_color = Color.Green;
    let red_color: Color = Color[2] // let red_color = Color[2];
    
    // 函数返回值一般不用定义,ts会自动理解
    function a(name: string): void{} // function a(name){}
    
    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
// 类型注释【:】
let boolenVal: boolen = true // var boolenVal = true
let num: number = 123 // var num = 123
let str: string = 'abc' // var str = 'abc'

// 泛型写法
let list1: Array<number> = [1,2,3] // let list1 = [1, 2, 3];
// 默认写法
let list2: number[] = [1,2,3]

// 元组写法:数组长度不变
let list: [number, number, number] = [1,2,3] // var list = [1, 2, 3];

let symbolVal = Symbol(); // 输出的结果和ts写法一致
let obj = {
  [symbolVal]: "abc"
}

enum Color {Red = 2, Green, Blue} // 如果值为number 可以自动递增赋值;其他情况必须全部手动赋值。
/*
(function (Color) {
  Color[Color["Red"] = 2] = "Red";
  Color[Color["Green"] = 3] = "Green";
  Color[Color["Blue"] = 4] = "Blue";
})(Color || (Color = {}));
*/
let green_color: Color = Color.Green // let green_color = Color.Green;
let red_color: Color = Color[2] // let red_color = Color[2];

// 函数返回值一般不用定义,ts会自动理解
function a(name: string): void{} // function a(name){}
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

# 联合类型

如果可能存在多个类型需要组合,就可以使用联合类型(union);

使用时需要手动添加判断类型

function aa(id: number | string | boolean){
  // console.log(id.toLocaleLowerCase()); // 报错
  if(typeof id == "string"){
    console.log(id.toLocaleLowerCase());
  }else{
    console.log(id);
  }
}
1
2
3
4
5
6
7
8

# 类型别名

使用type来定义类型,可以实现类型的复用

type Point = {
  x: number,
  y: number | string
}
// 不可以添加
// type Point = {} // 会报错
// 可以扩展
type threePoind = Point & {
  z: number
}
1
2
3
4
5
6
7
8
9
10

# 接口

功能、使用和类型别名类似,支持扩展(extend),还支持添加。

需要注意的是:即使是后面添加的参数,在前面先使用的类型中,也需要有对应参数。

interface Point {
  x: number,
  y: number | string
}
// 可以添加
interface Point {
  c: number, // 相当于Point有了三个参数:x,y,c
}
// 可以扩展
interface threePoind extends Point {
  z: number
}
1
2
3
4
5
6
7
8
9
10
11
12

# 类型断言

当语句返回ts不认识当类型时,需要使用类型断言帮助ts识别类型。只能更具体。

// const myCanvas = document.getElementById('my-canvas');
const myCanvas = document.getElementById('my-canvas') as HTMLCanvasElement
// 只能更具体
const x = 'hello' as number // 会报错,因为已经识别为string了
const x = ('hello' as unknown) as number // 可以执行
1
2
3
4
5

# 文字类型

当需要限制值需要符合条件:属于某一组值时,可以使用文字类型去限制。

function printString(str1: string, str2: 'left' | 'right' | 'center'){}
printString('leo', '11') // 类型“"11"”的参数不能赋给类型“"left" | "right" | "center"”的参数
printString('leo', 'left')
1
2
3

# 类型缩小

实际上就是js做区分的种种办法。

  1. typeof 类型守卫:使用typeof判断类型然后做对应操作
  2. 真值缩小:使用 boolean 类型去判断传入值的 true / false
  3. 等值缩小:使用 === !=== == != 判断不同值应该如何处理
  4. in 操作符缩小:value in Type 来判断 Type 中是否有改属性
  5. instanceof 操作符缩小: 查看是否是XX的实例
  6. 分配缩小:使用三元运算来隐式的给参数设置类型
function print(strs: string | string[] | null, str1: string | number){
  // 为什么null的判断需要放在前面,因为null的类型也是object,放到后面,会导致 for of时 strs可能为null
  if(!strs){return} // 真值缩小 先过滤掉null等
  // if(strs && typeof strs == 'object'){ // 或者在 object的时候判断null
  if(typeof strs == 'object'){ // 类型守卫
    for (const str of strs) {
      console.log(str);
    }
  } else if(typeof strs == 'string'){
    if(strs === str1){ //等值缩小
    }else{
    }
  } else {
  }
}
// in 操作符
type Fish = { swin: ()=> void }
type Bird = { fly: ()=> void }
type Man = { fly?: ()=> void, swin?: ()=> void } 
// 增加Man类型之后,ts需要用as来断言 使检查通过,但是不代表js一定不报错
// 假设参数为空对象{}那么ts通过,但js会报错
function move(animal: Fish | Bird | Man){
  if("swin" in animal){
    return (animal as Fish).swin()
  }
  return (animal as Bird).fly()
}
// instanceof
function logVal(x: Date | string){
  if(x instanceof Date){
    console.log(x.toUTCString());
  } else {
    console.log(x.toString());
  }
}
let x = Math.radom() < 0.5 ? 10 : '123' // 分配缩小 => x: number | string
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

# 控制流分析

假设一个函数可以返回3种类型(现式),在调用函数时,如果由于逻辑操作,使得返回值类型被限制为2种,那么返回值的类型

function getX(){
  const a = Math.random()
  let x: string | number | boolean = a < 0.5
  if(x){
    return 'test'
  }
  return 100
}
let a_x = getX()
a_x = '1'
a_x = 1
a_x = true // 报错:a_x 类型 string | number 不可以赋值 boolean
1
2
3
4
5
6
7
8
9
10
11
12

# 使用类型谓词

定义一个函数,使返回值是一个类型谓词。

type Fish = {
  name: string,
  swim: () => void
}

type Bird = {
  name: string,
  fly: () => void
}

function isFish(pet: Fish | Bird): pet is Fish{  // 类型谓词
  return (pet as Fish).swim !== undefined
}

function getSmallPet(): Fish | Bird{
  let fish: Fish = {
    name: 'sharkey',
    swim: ()=>{}
  }
  let bird: Bird = {
    name: 'sparrow',
    fly: ()=>{}
  }
  return Math.random() > 0.5 ? bird : fish
}

let pet = getSmallPet()

if(isFish(pet)){
  pet.swim()
}else{
  pet.fly()
}

const zoo: (Fish | Bird)[] = [getSmallPet(),getSmallPet(),getSmallPet(),getSmallPet(),getSmallPet()]
const underWater: Fish[] = zoo.filter(isFish)
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]
const underWater3: Fish[] = zoo.filter((pet): pet is Fish=>{
  if(pet.name == 'book'){
    return false
  }
  return isFish(pet)
})
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

# 函数

# 函数类型表达式

let fn: (a: string) => void // void 表示 返回为空
type FunctionA = (a: string) => void
let fnA: FunctionA
1
2
3

# 调用签名

给函数增加标签来让ts识别函数是某种类型的

type Callback = { // 实际还是一个函数,disc为标记函数的属性
  disc: string,
  (someArg: number): boolean // 表示函数的入参时number类型,返回值时boolean类型的
}

function doA(fn: Callback){
  console.log(fn.disc + ' returen ' + fn(6));
}

function callbackA(n:number){
  console.log(n);
  return n > 1
}
callbackA.disc = 'hellow' // 给函数赋值标记 让ts识别callback是Callback类型的
doA(callbackA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 构造签名

在调用函数签名前增加new关键字来实现,表示函数使用时 需要先 new。

class Ctor{
  s:string
  constructor(s: string){
    this.s = s
  }
}
type SomeConstructor = {
  new (s: string): Ctor // 带new才是构造签名
}

function fnB(ctor: SomeConstructor){
  return new ctor('hello') // 使用参数时需要new
}

const fB = fnB(Ctor)
console.log(fB.s);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 泛型函数

泛型就是两个值的类型存在关系,函数中,输入输出值间存在某种关系,用一个字符来表示这个不确定的类型的处理,就是泛型函数。

泛型也可以定义多个,用逗号间隔。

// 这里的 Type 不是固定写法,随便用什么代替都可以
function firstElement<Type>(arr: Type[]): Type | undefined{
  return arr[0]
}
// 多个泛型
function map<Input, Output>(arr: Input[], func: () => Output): Output[] {
  return arr.map(func)
}
1
2
3
4
5
6
7
8

# 限制条件

因为泛型表示了不确定的类型,当需要对该类型进行限制的时候,就可以使用extend关键字来进行继承另一种类型。

function longest<Type extends { length: number }>(a: Type, b: Type){
  if(a.length >= b.length){
    return a
  }
  return b
}
// 下面的参数里都有length属性,所以都可以通过
const longestArr = longest([1,2], [2,3,4,1])
const longestStr = longest('111', '34423')
const longestObj = longest({a: 1, length: 2}, {a: 1, length: 3})
1
2
3
4
5
6
7
8
9
10

注意: 当返回值的类型也是泛型时,如果实际代码逻辑的返回值不是泛型而仅包含泛型的限制条件 也是不对的。

# 指定类型参数

泛型是不确定的类型,调用时,可以指定泛型的类型。

function combine<Type>(arr1: Type[], arr2: Type[]): Type[]{
  return arr1.concat(arr2)
}
const arr_combine = combine([1,2,3], [3, 4])
const arr_combine_1 = combine([1,2,3], ['3', '4']) // 报错,在类型推断时,第一个参数确定了泛型Type是 array[],而第二个是 string[],得到类型不一致的错误
const arr_combine_2 = combine<string | number>([1,2,3], ['3', '4']) // 调用时确定泛型的实际类型
1
2
3
4
5
6

类型参数使用准则:

  1. 尽可能使用类型参数本身,而不是对其进行约束
  2. 尽可能少得使用类型参数
  3. 如果类型参数只出现一次,请确认是否需要

# 函数重载

多个重名的函数,没有实现部分的叫做重载函数,否则叫实现函数,这样可以满足函数传递不同的参数

// 重载函数:函数只有这两种传递参数的形式
// 下面代码中 使用时只能传递1/3个参数,如果数量不对,ts也会报错
function makeDate(timestamp: number): Date
function makeDate(m: number, d: number, y: number): Date
// 实现函数
function makeDate(mOrTimestanp: number, d?: number, y?: number): Date { // 这样写是为了兼容重载函数的参数
  if(d !== undefined && y !== undefined){
    return new Date(y, mOrTimestanp ,d)
  }
  return new Date(mOrTimestanp)
}
1
2
3
4
5
6
7
8
9
10
11

# 属性 / 参数

# 可选属性

  • 如果遇到不确定是否传递的参数,可以使用 ? 来表示
  • function a(name?: string): void{
      console.log(name?.toString()) // 同理在取值的时候也需要使用?
    }
    
    1
    2
    3
function a(name?: string): void{
  console.log(name?.toString()) // 同理在取值的时候也需要使用?
}
1
2
3

注意: 回调的函数类型一定不要用可选参数,除非你不打算传递参数。(防止被别人调用时出现异常)

  • 如果遇到参数一定不是 undefined 或 null 时,可以使用 ! 来表示
  • function a(name: number | null): void{
      console.log(name!.toFixed()) // 没有!时会提示,当name时null时 没有toFixed函数
    }
    
    1
    2
    3
function a(name: number | null): void{
  console.log(name!.toFixed()) // 没有!时会提示,当name时null时 没有toFixed函数
}
1
2
3

# 只读属性

属性前增加 readonly 修饰符的,用作只读属性。只读属性只控制当前属性,对象的子属性需要手动修改是否只读。

interface someType{
  readonly prop: string,
  readonly father: { // 只读
    child1: sting // 可读可写
  }
}
function doSth(obj: someType){
  obj.prop = '1' // 报错,只读属性不可修改
  obj.father = { child:123} // 报错
  obj.father.child = '123' // 正确 
}
1
2
3
4
5
6
7
8
9
10
11

# 索引签名

就是给属性名也增加一个类型判断

interface StringArray{
  [index: number]: string // [] 创建索引建明
}
const myArray: StringArray = ['a', 'b']
const secondItem = myArray[0]

interface TestString{
  readonly [a: string]: boolean // 也可以和只读一起使用
  x: boolean
}
const testString: TestString = {
  x: true,
  ['xa']: true,
  [1]: true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 扩展类型

# 接口扩展 与 交叉类型

接口扩展:接口 extends 另一个接口,新的接口带有老接口的全部参数。

interface Person{
  name: string,
  age: number,
}
interface Student{
  work: string,
}
interface Boy extends Person{
  sex: 'man',
}
const boy_a: Boy = { // 参数需要复合所有接口的
  name: 'lilei',
  age: 10,
  sex: 'man'
}
interface Boy1 extends Person, Student{ // 可以扩展自多个可接口
  sex: 'man',
}
const boy_b: Boy1 = {
  name: 'lilei',
  age: 10,
  sex: 'man',
  work: 'read book'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

交叉类型:功能与 extends 同时扩展自多个接口 一致,只是使用 type 写法; 或者在使用 联合类型(|) 语境时,实现交叉功能。

type Boy2 = Person & Student
1

区别:仅在于语法环境,功能一致。

# 泛型类型

和泛型函数同理,目的是为了解决不确定参数的真实类型,用泛型临时替代,在调用时,传递真实类型以便于ts理解代码。

如果使用unknown或者any,ts就无法判断类型上是否有某个函数

interface Box<Type>{
  con: Type
}
const redBox: Box<number> = { // 相当于把number 传参给interface,这样可以有效减少代码量
  con: 123
}
1
2
3
4
5
6

# 类型创建类型

# 泛型

类似于函数的语法来传递类型,即通过类型创建类型。

除了上面讲到的 泛型函数、泛型参数、泛型约束,还有泛型接口可以使用:

// 使用泛型
function identity<Type>(arg: Type): Type{
  return arg
}
let identity_a: <Type>(arg: Type) => Type = identity 
let identity_b: <Input>(arg: Input) => Input = identity 
let identity_c: { <Type>(arr: Type): Type } = identity 

// 泛型接口
interface GenericIdentity {
  <Type>(arg: Type): Type
}
let my_identity_a: GenericIdentity = identity

// 也可以把泛型写在接口后面,调用时传递进来
interface GenericIdentityFn<Type> {
  (arg: Type): Type
}
let my_identity_b: GenericIdentityFn<string> = identity

// 泛型类
class GenericNumber<Type> {
  val1: Type
  constructor(opt: {
    val1: Type
  }){
    this.val1 = opt.val1
  }
}
let myGenericNumber = new GenericNumber({val1: 1}) 

// 泛型约束 extends
interface LengthWise {
  length: number
}
function identitylog<Type extends LengthWise>(arg: Type): Type{
  console.log(arg.length);
  return arg
}
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

泛型的 extends 关键字只能解决类型与泛型之间的关系,但是还不够,如果需要在泛型中使用类型的参数,就需要使用 keyof 关键字:

// 泛型约束中使用类型参数
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key){ // 表示 Key 的可选值需要属于 Type 的属性
  return obj[key]
}
let obj_a = { a: 1, b: 2, c: 3 }
getProp(obj_a, 'a') // 正确
// getProp(obj_a, 'd') // 类型“"d"”的参数不能赋给类型“"a" | "b" | "c"”的参数
1
2
3
4
5
6
7

泛型中还可以使用类类型来约束:

// 泛型中使用类类型
// 没有约束
// function create<Type>(c: { new (): Type }): Type{
//   return new c()
// }
class BeeKeeper{
  hasMask: boolean = true
}
class ZooKeeper{
  nameTag: string = 'leo'
}
class Animal{
  numLegs: number = 4
}
class Bee extends Animal{
  keeper: BeeKeeper = new BeeKeeper()
}
class Lion extends Animal{
  keeper: ZooKeeper = new ZooKeeper()
}
// 添加约束 Animal
function createInstance<A extends Animal>(c: { new (): A }): A{
  return new c()
}
createInstance(Lion).keeper.nameTag
createInstance(Bee).keeper.hasMask
createInstance(Animal).numLegs
// createInstance(BeeKeeper) //类型“typeof BeeKeeper”的参数不能赋给类型“new () => Animal”的参数。
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

# keyof操作符

上面【泛型约束中使用类型参数】中有提到过,实际上 keyof 操作符是吧对象属性取出,然后作为联合类型使用。

type TestPoint = {
  x: number,
  y: number
}
type Position = keyof TestPoint // 相当于 type Position = 'x' | 'y'
1
2
3
4
5

# typeof操作符

和js中的一致,但是通过组合其他操作符可以表达跟多情景

type Predicate = (x: unknown) => boolean
type K = ReturnType<Predicate> // 预定义K类型是 Predicate 的这个函数类型

function f(){
  return 1
}
type P = ReturnType<typeof f> // 因为f是函数 所以去函数的类型 赋给 P
1
2
3
4
5
6
7

# 索引访问类型

同js ,可以通过索引访问类型某种属性的类型

type Person1 = {
  age: number,
  name: string
}
type a = Person1['age']
let persona: a = '' // 不能将类型“string”分配给类型“number”
type b = Person1['age' | 'name'] // 也可以使用 | 关键字表示联合类型
let personb: b = true // 不能将类型“boolean”分配给类型“b”
type c = Person1[typeof Person1] // 也可以使用 typeof 关键字表示所有属性值
1
2
3
4
5
6
7
8
9

# 条件类型

SomeType extends OtherType ? TrueType : FalseType

类似于js中的三元表达式,功能含义也一致,只是参数换成了类型

// 使用重载比较麻烦
interface IdLabel { id: number }
interface NameLabel { name: string }
function createLabel(id: number): IdLabel
function createLabel(name: string): NameLabel
function createLabel(nameOrId: number | string): IdLabel | NameLabel{
  if(typeof nameOrId == 'number') {
    return {id: nameOrId}
  }
  return {name: nameOrId}
}

type NameOrId<T> = T extends number ? IdLabel : NameLabel
function createLabel1<T extends number | string>(nameOrId: T): NameOrId<T>{
  if(typeof nameOrId == 'number') {
    // return {id: nameOrId} // TODO 这里没搞明白为啥返回的与实际类型时相符的,但是这里报错
  }
  // return {name: nameOrId}
  throw ''
}
let createLabelA = createLabel1('aa')
// let createLabelB = createLabel(true) // wrong
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#

# 使用类

类的基本使用方法与es6一致,只是增加了类型限制,具体使用如下:

class PointX{
  x: number = 0 // 初始化:可以直接赋值 
  y: number
  readonly z: string // 只读属性
  public a: number = 0 // 默认 公开属性
  protected b: number = 0 // 能在当前类和子类中访问
  private c: number = 0 // 私有 只能在当前类中访问
  static d: number = 100 // 静态成员 编译后 生成 PointX.d = 100;
  private static e: number = 100 // 静态成员 可以和上面的一起使用
  static #f: number = 10 // 私有的静态成员,不能通过类调用 // 生成 _PointX_f = { value: 10 };
  _length: number = 0
  constructor(val: string = '1'){ // 初始化:构造函数
    this.y =  0
    this.z = val // 只读属性在构造函数中可以初始化
  }
  log(n: number): void{ // 和普通函数一样
    // this.z = '22' // 无法分配到 "z" ,因为它是只读属性。
  }
  // getter / setter
  get length(): number{ // number可以不写 能自动推断出
    return this._length
  }
  set length(val){ // 如果属性没set 会推断为只读属性
    this._length = val
  }
} 
const pt = new PointX('a') // 构造函数中需要传递参数,实例化的时候就必须按照规则传参
pt.x = 1
pt.y = 2
// pt.z = '123' // 无法分配到 "z" ,因为它是只读属性。
const pt1 = new PointX() // constructor中设置了默认值时 也可不填参数

// 继承
class PointTest extends PointX{
  constructor(){
    // 派生类的构造函数必须包含 "super" 调用。
    super()
  }
  // 索引签名, 和上面的索引签名一致,用来规范对象中的参数、函数等
  [s: string]: number | string | ((s: number) => void)
  a: number = 1
}

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

# 类的继承

implements 子句,用来检查类与接口的类型 是否规则兼容(不一定一致,只要能兼容就可以)

interface Pingable{
  ping(): void
}
// implements 用来检查 类 与 接口
class Sonar implements Pingable{
// class Sonar implements Pingable, Pongable{ // 支持多个
  ping(): void {
    console.log(1);
  }
}
1
2
3
4
5
6
7
8
9
10

extends 子句,用来继承,时类与类之间的关系,与es6一致

// 和上面的一致
class PointTest extends PointX{
  constructor(){
    // 派生类的构造函数必须包含 "super" 调用。
    super()
  }
  log(){
    // 因为重名 于是 重写方法
    super.log() // 还可以调用 基类 的方法
  }
}
1
2
3
4
5
6
7
8
9
10
11

注意:默认初始化顺序是 基类字段初始化 => 基类函数执行 => 派生类字段初始化 => 派生类函数执行

# 使用泛型

类同样支持泛型 用法一致

class Box<Type>{
  contents: Type
  // static a: Type // error 静态成员不能引用类类型参数。
  constructor(val: Type){
    this.contents = val
  }
}
const b = new Box<string>('hello')
1
2
3
4
5
6
7
8

# this类型

this类型指向的是调用函数所属的原型的类型

//  this类型
class BoxA {
  cont: string = ''
  sameAs(oth: this){ // 这里oth指向boxa 但是this指向的是derviedboxa,解析:
    // 因为derviedboxa类型是 DerivedBoxA 而boxa上没没有类型DerivedBoxA包含的参数othCont,所以报错
    return oth.cont === this.cont // 还没到这一步
  }
}
class DerivedBoxA extends BoxA{
  othCont: string = '' 
}
const boxa = new BoxA()
const derviedboxa = new DerivedBoxA()
derviedboxa.sameAs(boxa) // error // 类型“BoxA”的参数不能赋给类型“DerivedBoxA”的参数。 //   类型 "BoxA" 中缺少属性 "othCont",但类型 "DerivedBoxA" 中需要该属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 基于类型守卫的this

this is Type // Type是任意值, this is是固定写法

守卫和if结合起来,可以精细的让ts识别变量的类。

class FileSys{
  isFile(): this is FileRep {
    return this instanceof FileRep
  }
  isDirectory(): this is Directory{
    return this instanceof Directory
  }
  isNetworked(): this is Networked & this{
    return this.networked
  }
  constructor(public path: string, private networked: boolean){}
}
class FileRep extends FileSys{
  constructor(path: string, public content: string){
    super(path, false)
  }
}
class Directory extends FileSys{
  children: FileSys[]
  constructor(path: string, public content: string){
    super(path, false)
    this.children = []
  }
}
class Networked{
  host: string = ""
}

const fso: FileSys = new FileRep('/document/test.html', '<html>')
if(fso.isFile()){ // 根据方法的return 获得this is后面的类,来告诉ts 当前的参数是XX类的,可以使用这个类的方法
  console.log(fso.content);
}else if(fso.isDirectory()){
  console.log(fso.children);
}else if(fso.isNetworked()){
  console.log(fso.host);
}
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

# 参数属性

类定义参数的时候,也可以直接在构造函数中使用 public 等关键字,使得ts可以识别实例化对象中存在该属性

class Params{
  constructor(public x: number){ // pubic

  }
}
const p = new Params(1)
console.log(p.x);
1
2
3
4
5
6
7

# 类表达式

可以写匿名类,然后赋值后使用, 和直接定义class 功能一致

const someClass = class<Type> { // 匿名类
  cont: Type
  constructor(val: Type){
    this.cont = val
  }
}
const someClassA = new someClass('hello')
1
2
3
4
5
6
7

# 抽象类和成员

abstract 关键字,实现功能:创造一个不可以被实例化的类,只能作为基类去继承。

abstract class Base { // 抽象类
  abstract getVal(): string // 抽象方法需要在 子类中定义同名函数,需要复合基类的类型规则
  logVal(){
    console.log(this.getVal())
  }
}
1
2
3
4
5
6

# 模块

# ES语法

ES模块使用语法 与js保持一致

// ts-test/utils/regexp.ts
export default {
  isPhone(phone_num: string, country_code?: string): boolean{
    let regexp: RegExp | null = null
    switch(country_code){
      default:
      case 'cn': regexp = /^((13\d)|(14[5,7,9])|(15[0-3,5-9])|(166)|(17[0,1,3,5,7,8])|(18[0-9])|(19[8,9]))\d{8}/; break;
    }
    return regexp.test(phone_num)
  }
}
export let test = '100'
export type AA = {
  val: 'aa'
}
// ts-test/02.ts
import RegExpTools, {test as TestVal} from './utils/regexp'
// import AA from './utils/regexp' // 也可以定义一个type/interface 然后又导出
// import type AA from './utils/regexp' // 加上type 也可以
function checkPhone(str: string){
  return RegExpTools.isPhone(str)
}
checkPhone(TestVal)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 总结

TS 的入门课程已经看完,后面有时间可以想一想怎么在老代码上比较便捷的增加ts