TS 的基础概念

什么是 TS

  • JS 的一个超集,在原有的语法基础上,添加强类型并切换为基于类的面向对象语言
  1. 面向项目
    TS - 面向解决大型的复杂项目、架构、代码维护复杂场景
    JS - 脚本化语言,用于面向简单页面场景
  2. 自主检测
    TS - 编译时,主动发现并纠正错误
    JS - 运行时,执行报错
  3. 类型检测
    TS - 强类型语言,支持动态和静态的类型检测
    JS - 弱类型语言,无静态类型选项
  4. 运行流程
    TS - 依赖编译,依靠编译打包实现在浏览器端的运行
    JS - 可直接在浏览器端运行
  5. 复杂特性
    TS - 模块化、接口、泛型

TS 基础类型和语法

  • boolean、string、number、array、null、undefined、tuple
1
2
3
4
5
6
7
8
9
let isEnable: boolean = false;
let className: string = "baidu";
let classNum: number = 2;
let u: undefined = undefined;
let n: null = null;
// 统一方式 & <>方式
let classArr: string[] = ["basic", "execute"];
let classArr: Array<string> = ["basic", "execute"];
let tupleType: [string, boolean] = ["basic", false]; // tuple

enum

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
// 数字型枚举 - 默认从0开始,依次递增
enum Score {
BAD, // 0
NG, // 1
GOOD, // 2
}

let score: Score = Score.BAD;

// 字符串类型枚举
enum Score {
BAD = "BAD",
NG = "NG",
GOOD = "GOOD",
}

// 反向映射
enum Score {
BAD, // 0
NG, // 1
GOOD, // 2
}

let scoreName = Score[0]; // BAD
let scoreVale = Score["BAD"]; // 0

// 异构
enum Enum {
A, // 0
B, // 1
C = "C",
D = "D",
E = 8,
F, // 9
}

let Enum;
(function (Enum) {
// 正向
Enum["A"] = 0;
Enum["B"] = 1;
Enum["C"] = "C";
Enum["D"] = "D";
Enum["E"] = 8;
Enum["F"] = 9;

// 逆向
Enum[0] = "A";
Enum[1] = "B";
Enum[8] = "E";
Enum[9] = "F";
})(Enum || (Enum = {}));

object / Object / {}

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
// object - 非原始类型
interface ObjectConstructor {
create(o: object | null): any;
}

const proto = {};

Object.create(proto);
Object.create(null);
Object.create(undefined); // Error

// Object
// Object.prototype 上的属性
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
}

// 定义了Object类属性
interface ObjectConstructor {
new (value: any): Object;
readonly prototype: Object;
}

// {} - 定义空属性对象
const obj = {};

obj.prop = "props"; // Error
obj.toString(); // OK

接口 - interface

对行为模块的抽象,具体的行为是由类来实现

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
// 描述对象内容
interface Class {
name: string;
time: number;
}

let baidu: Class = {
name: 'typescript',
time: 2
}

// 只读
interface Class {
readonly name: string;
time: number;
}

let arr: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = arr;

ro[0] = 12; // 赋值 - Error
ro.push(5); // 增加 - Error
ro.length = 10; // 长度改写 - Error
arr = ro; // 覆盖 - Error

// 任意可添加属性
interface Class {
readonly name: string;
time: number;
[propName: string]: any;
}

const c1 = { name: "JS" };
const c2 = { name: "browser", time: 1 };
const c3 = { name: "ts", level: 1 };

interface vs type

  1. type 不能被继承或者实现 (& 可不用理解为继承) ,interface 可以被继承或者实现,但可以合并
  2. type 可以定义任何类型,包括联合类型、交叉类型、字面量类型、原始类型等。interface 只能定义对象类型,包括属性、方法、索引等
  3. type 通常用于为复杂类型创建别名,以方便在代码中使用。interface 通常用于定义某个实体的结构,以及实现该结构的对象或类

交叉类型 - &

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
// 合并
interface A {
inner: D;
}
interface B {
inner: E;
}
interface C {
inner: F;
}

interface D {
d: boolean;
}
interface E {
e: string;
}
interface F {
f: number;
}

type ABC = A & B & C;

let abc: ABC = {
inner: {
d: false,
e: "className",
f: 5,
},
};

// 合并冲突
interface A {
c: string;
d: string;
}
interface B {
c: number;
e: string;
}

type AB = A & B;

let ab: AB;
// 合并的关系是'且' => c - never

断言 - 类型的声明和转换

编译时作用

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
// 尖括号形式
let anyValue: any = 'hi baidu';
let anyLength: number = (<string>anyValue).length;

// as声明
let anyValue: any = 'hi baidu';
let anyLength: number = (anyValue as string).length;

// 非空判断 - 只确定不是空
type ClassTime = () => number;

const start = (ClassTime: ClassTime | undefined) {
// 业务逻辑
// if (额外判断逻辑) {
let time = classTime!(); // 具体类型待定,但是非空确认
// }
}

// 问题
const tsClass: number | undefined = undefined;
const baidu: number = tsClass!;
console.log(baidu);

// 转义成
const tsClass = undefined;
const baidu = tsClass;
console.log(baidu); // undefined

// 肯定断言 - 肯定化保证赋值
let score!: number;
startClass();
console.log('' + score); // 使用前赋值

function startClass() {
score = 5;
}

类型守卫

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
// in - 定义属性场景下内容的确认
interface Teacher {
name: string;
courses: string[];
}
interface Student {
name: string;
startTime: Date;
}

type Class = Teacher | Student;

function startCourse(cls: Class) {
if ('courses' in cls) {
console.log("Courses:" + cls.courses);
}
if ('startTime' in cls) {
console.log("startTime:" + cls.startTime);
}
}

// typeof / instanceof - 类型分类场景下的身份确认
function class(name: string, score: string | number) {
if (typeof score === "number") {
return "teacher:" + name + ":" + score;
}
if (typeof score === "string") {
return "student:" + name + ":" + score;
}
}

const getName = (cls: Class) => {
if(cls instanceof Teacher) {
return cls.courses;
}
if(cls instanceof Student) {
return cls.startTime;
}
}

// 自定义类型
const isTeacher = function (cls: Teacher | Student): cls is Teacher {
return 'courses' in cls;
}

const getName = (cls: Teacher | Student) => {
if(isTeacher(cls)) {
return cls.courses;
}
}

// 类型别名 & 联合类型

never 的作用

类型收缩、检查错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Method = 'GET' | 'POST';
function request(method: Method, url: string) {
switch(method) {
case 'GET'
return 1;
case 'POST'
return 2;
default
const n: never = method;
return n;
}
}
// 后续若Method添加其他选项,函数会报错,利于检查

// 永不能执行完 or 永远error
function errorGen(msg: string): never {
throw new Error(msg);
}

function infiniteLoop(): never {
while (true) {
// 业务逻辑
}
}

any、unknown

  1. any 和 unknown 都是顶级类型,但是 unknown 更加严格,不像 any 那样不做类型检查
  2. unknown 因为未知性质,不允许访问属性,不允许赋值给给 any 和 unkown 之外的类型变量
  3. 联合类型中的 unkown 为 unkown,意外是 any 类型。如果至少一种组成类型是 any,联合类型会相当于 any
  4. 交叉类型中的 unkown 为其他
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let foo: any = 123;
console.log(foo.msg); // 符合TS的语法
let a_value1: unknown = foo; // OK
let a_value2: any = foo; // OK
let a_value3: string = foo; // OK

let bar: unknown = 222; // OK
console.log(bar.msg); // Error
let k_value1: unknown = bar; // OK
let K_value2: any = bar; // OK
let K_value3: string = bar; // Error

type UnionType1 = unknown | null; // unknown
type UnionType2 = unknown | undefined; // unknown
type UnionType3 = unknown | string; // unknown
type UnionType4 = unknown | number[]; // unknown
type UnionType5 = unknown | any; // any

type IntersectionType1 = unknown & null; // null
type IntersectionType2 = unknown & undefined; // undefined
type IntersectionType3 = unknown & string; // string
type IntersectionType4 = unknown & number[]; // number[]
type IntersectionType5 = unknown & any; // any

关键字解释

typeof

用于类型表达时,用于从一个变量上获取它的类型

1
2
3
4
5
6
7
8
9
const func = (a: number, b: number): number => {
return a + b;
};
const obj = {
name: "jack",
age: 11,
};
type tupeFunc = typeof func; // type tupeFunc = (a: number, b: number) => number
type obj = typeof obj; // type obj = { name: string; age: number;}

keyof

将一个类型映射为它所有成员名称的联合类型

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
// 用 TS interface 描述对象
interface TsInterfaceObject {
first: string;
second: number;
}
// 用 TS type 描述对象
type TsTypeObject = {
first: string;
second: number;
};
// 用 Ts type 描述基本类型别名
type TsTypeAlias = string;
class JsClass {
private priData: number;
private priFunc() {}
public pubData: number;
public pubFunc1() {}
public pubFunc2() {}
}
type NameUnionOfInterface = keyof TsInterfaceObject; // "first" | "second"
type NameUnionOfTypeObj = keyof TsTypeObject; // "first" | "second"

/**
* 对一个非对象类型使用 keyof 后,会返回其 prototype 上所有 key 组合成的一个联合类型
*/
type NameUnionOfTypeAlias = keyof TsTypeAlias; // number | typeof Symbol.iterator | "toString" | "charAt" | ...

/**
* 其实也是返回 prototype 上所有 key(因为其实 private 私有部分实际是放在实例化对象中,而非原型)
*/
type NameUnionOfClass = keyof JsClass; // pubData" | "pubFunc1" | "pubFunc2"

in

类似 for..in 遍历,[p in K] 相当于在遍历 K 的属性

extends

类型继承

interface 可用 extends 继承,type 不可以,可以同时继承多个 interface,它们之间用逗号分隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface IHuman {
gender: "male" | "female";
age: number;
}
interface IProgrammer {
language: "java" | "php" | "javascript";
}

interface IWebDeveloper extends IProgrammer, IHuman {
skill: "vue" | "react";
}
const Me: IWebDeveloper = {
skill: "react",
language: "javascript",
age: 18,
gender: "male",
};

类型约束

在书写泛型的时候,往往需要对类型参数作一定的限制

1
2
3
4
// entities 的每一项可以是一个对象,但是必须含有类型为string的cname属性
function getCnames<T extends { cname: string }>(entities: T[]): string[] {
return entities.map((entity) => entity.cname);
}

条件匹配

1
2
3
4
5
6
7
type OnlyWantNumber<T> = T extends number ? any : never;
type Type1 = OnlyWantNumber<number>; // type Type1 = any
type Type2 = OnlyWantNumber<string>; // type Type2 = never

// 如果 extends 左侧的类型为联合类型,会被拆解,<'x' | 'y'> => P<'x'> | P<'y'>
type P<T> = T extends "x" ? string : number;
type A3 = P<"x" | "y">; // A3的类型是 string | number

infer

可以在 extends 关键字右侧的类型表达式中的任何位置使用,为出现在该位置的任何类型命名

1
2
3
type Unpack<A> = A extends Array<infer E> ? E : A;
type Test = Unpack<Apple[]>; // => Apple
type Test = Unpack<Apple>; // => Apple

常用语法实现

Partial

将 T 中所有属性都变为 非必须的

1
2
3
4
5
type MyPartial<T> = {
// 1. keyof T 返回 T 类型的所有键组成的一个类型
// 2. in 遍历所有的 key
[P in keyof T]?: T[P];
};

Required

将 T 中所有属性都变为 必须的

1
2
3
4
5
type MyRequired<T> = {
// 1. 这里的 -?: 不能写成 :
// 2. 因为使用[K in keyof T]语法,K 中的属性会保留它自身在T中的可选性。即之前如果是必填的,在新类型中还是必填的,如果是选填的同理
[K in keyof T]-?: T[K];
};

ReadOnly

将 T 中所有属性都变成 仅读的

1
2
3
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};

Record

构造一个键类型属于 K(某种类型或集合),值的类型为 V 的新类型

1
2
3
4
5
// 1. 对K的类型定义使用的是 keyof any 等价于 string | number | symbol
// 2. 并且使用 extends 对K的类型进行约束
type MyRecord<K extends keyof any, V> = {
[P in K]: V;
};

Pick

从 T 中取出一部分属性 K 组成一个新的类型返回

1
2
3
4
// K 必须取是 T 的子集
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};

Omit

从 T 中剔除一部分属性 K 组成一个新的类型返回

1
2
3
4
// 1. 首先,先用 Keyof 拿到T中所有的属性
// 2. 再从中剔除掉属性 K。那么剩下的就是需要的属性,Pick一下
// 3. 这里要说明一下,为什么 K extends keyof any 要用 any。而不是像 Pick 一样用 K extends keyof T。因为 Pick的时候第二个参数必须是T中的某个属性,而 Omit 的时候不需要
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Exclude

将 U 联合类型的所有成员,从类型 T 中排除,可以理解为取差集

1
2
// extends 关键字左侧的类型为联合类型,会被拆解
type MyExclude<T, U> = T extends U ? never : T;

Extract

选取 T 类型和 U 类型两者的公共部分并返回为一个新类型,可以理解为取交集

1
2
// extends 关键字左侧的类型为联合类型,会被拆解
type MyExtract<T, U> = T extends U ? T : never;

NonNullable

从 T 中剔除 null | undefined 类型

1
type MyNonNullable<T> = T extends null | undefined ? never : T;

Parameters

基于一个函数参数的类型构造一个元组类型(tuple type)。所以这个工具类型的作用就是获取函数参数的类型

1
type MyParameters<T extends (...args: any) => void> = T extends (...args: infer P) => void ? P : never;

ConstructorParameters

把构造函数的参数类型作为一个元组类型返回

1
type MyConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never

TS 进阶

函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Course {
start(name: number, score: number): number;
start(name: string, score: string): string;
start(name: string, score: number): string;
start(name: number, score: string): string;
start(name: Combinable, score: Combinable) {
if (typeof name === "string" || typeof score === "string") {
return "student:" + name + ":" + score;
}
}
}

const course = new Course();
course.start("yunyin", 5);

泛型

  • 让模块可以支持多种类型数据 - 让类型声明和值一样,可以被赋值和传递
1
2
3
4
5
6
7
function startClass<T, U>(name: T, score: U): T {
return name + score;
}

console.log(startClass<String, Number>("yunyin", 5));

// T、U、K - 键值、V - 值、E - 节点、元素

获取对象键值约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const cat = {
name: 'kitty',
age: 3,
love: 'flower'
}

const user = {
loginId: 'abc',
loginPwd: '123456'
}

// 获取某个对象的某个属性值
function getValue<T extends object, K extends keyof T>(obj: T, name: K): T[K] {
return obj[name];
}

// 限制类型不为某个类型
type BanType<T, E> = T extends E ? never : T
function log<T>(x: BanType<T, Date>) {}

装饰器 - decorator

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
// tsc --target ES5 --experimentalDecorators
// "experimentalDecorators": true

// 类装饰器
function Baidu(target: Function): void {
target.prototype.startClass = function(): void {
// 通用功能
}
}

// 属性装饰器
function propsWrapper(target: Object, key: string) {
// 属性的统一操作
Object.defineProperty(target, key, {
})
}

// 方法装饰器 - target: Object, propertyKey: string, descriptor: TypePropertyDescript

@Baidu
class Course {
constructor() {
// 业务逻辑
}

@propsWrapper
public name: string;

@methodDec

}

使用 typeScript

引入和使用

webpack 打包配置

vue-cli - vue init/create ${myProject} ${template} => 配置 webpack => 编译时

a. entry - 入口
b. extentions 加上 ts 文件 area - 用于处理尝试的数据尾缀列表
c. loaders - ts-loader,增加对于 ts 的处理 => 工程化

TS 配置

tsconfig.json

vue / vuex + typescript

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
<template>
<div>
<vueComponent />
</div>
</template>

<script lang="ts">
// 1. 定义组件的方式上: 形式上 - extends
// const Component = {
// TS无法断定内部为vue组件,需要额外做申明处理 - Vue.prototype.xxxx
// }

// 申明当前组件模块 Vue.component or Vue.extend
import Vue from 'vue'
const Component = Vue.extend({
// 类型推断
})

// 2. 全面拥抱面向对象 - 官方vue-class-component
import Component from 'vue-class-component'

// @Component 本质 —— 类装饰器 => 利用装饰器,统一描述vue模板等概念
@Component({
template: '<vueComponent />'
})
export default class myComponent extends Vue {
message: string = 'Hello'
onClick(): void {
console.log(this.message);
}
}

// 3. 申明 - 利用ts的额外补充模块declare => 实现独立模块的声明,使之可以被独立引用
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
// 补充模块 - 通常使用.d.ts来做申明描述
declare module '/typings/vuePlugin.d.ts' {
interface Vue {
myProps: string
}
}
// 实例中使用
let vm = new Vue()
console.log(vm.myProps)

// 4. props - 提供propType原地声明复合变量
import { propType } from 'vue'

interface customPayload {
str: string,
number: number,
name: string
}

const Component = Vue.extend({
props: {
name: String, // 字符串类型
success: { type: String }, // 普通对象类型
payload: {
type: Object as propType<customPayload>
},
callback: {
type: Function as propType<() => void>
}
}
})

// 5. computed 以及 method中包含this且有return的方法 需要声明返回类型
computed: {
getMsg(): string {
return this.click() + "!"
}
}

methods: {
click(): string {
return this.message + 'baidu'
}
}

// 6. vuex的接入ts - 声明使用
// vuex.d.ts 声明模块 - ComponentCustomProperties
import { ComponentCustomProperties } from 'vue'

declare module '@vue/runtime-core' {
interface State {
count: number
}

interface ComponentCustomProperties {
$store: Store<State>
}
}

// 7. api形式编码实现 - 官方推荐
// store.ts
import { InjectionKey } from 'vue'
import {
createStore,
Store
} from 'vuex'

export interface State {
count: number
}

export const key: InjectionKey<Store<State>> = Symbol()

export const store = createStore<State>({
state: {
count: 0
}
})
// #######################
// main.ts
import { createApp } from 'vue'
import { store, key } from './store'

const app = createApp({
// 传入参数
})

// 利用了provider & inject
app.use(store, key) // => 传入injection Key => vue高级使用里会提到vue.use

app.mount('#app')

// ########################
// 消费方
import { useStore } from 'vuex'
import { key } from './store'

export default {
const store = useStore(key)

store.state.count
}
// 标准接口形式的功能引入,核心利用了vue的provide&inject

// 8. vuex面向对象 - 使用vuex-class
import { State, Action, Getter } from "vuex-class"

export default class App extends Vue {
// 属性装饰器,整合了store状态
@State login: boolean;

// 事件装饰器,整合了store方法
@Action setInit: () => void;

get isLogin: boolean;

mounted() {
this.setInit();
}
}
</script>

参考资料

TypeScript 体操