联合类型和类型保护

使用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
interface Dog {
canRun: boolean;
bark: () => {};
}

interface Cat {
canRun: boolean;
miaow: () => {};
}

/**
* @Description: 使用断言进行类型保护,解决联合类型的问题
* @Author:
* @param {Dog | Cat} animal 形参animal的类型是联合类型,只要满足其中一个类型即可
* @return {*}
* @Date: 2023-08-08 14:04:42
*/
function testAnimal(animal: Dog | Cat) {
// animal.miaow(); // 类型“dog”上不存在属性“miaow”。ts(2339),因为animal的类型是联合类型,只有dog类型才有miaow方法
// 类型保护
if (animal.canRun) {
// 将animal断言为dog类型即可使用dog类型的方法
(animal as Dog).bark()
} else {
// 将animal断言为cat类型即可使用cat类型的方法
(animal as Cat).miaow()
}
}

使用in关键字进行类型保护

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
interface Dog {
canRun: boolean;
bark: () => {};
}

interface Cat {
canRun: boolean;
miaow: () => {};
}

/**
* @Description: 使用in关键字进行类型保护,解决联合类型的问题
* @Author:
* @param {Dog | Cat} animal
* @return {*}
* @Date: 2023-08-08 14:10:07
*/
function testAnimal2(animal: Dog | Cat) {
// in关键字判断某个属性是否在某个对象上
if ("bark" in animal) {
animal.bark()
} else {
animal.miaow()
}
}

使用typeof关键字进行类型保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type NumOrString = string | number;

/**
* @Description: 使用typeof关键字进行类型保护,解决联合类型的问题
* @Author:
* @param {NumOrString} first
* @param {NumOrString} second
* @return {NumOrString}
* @Date: 2023-08-08 14:16:18
*/
function add(first: NumOrString, second: NumOrString): NumOrString {
// return first + second // 报错,因为NumOrString类型不确定,可能是string也可能是number,所以不能相加
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`
} else {
return first + second
}
}

使用instanceof关键字进行类型保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class NumberObj {
constructor(public count: number) { }
}

/**
* @Description: 使用instanceof关键字进行类型保护,解决联合类型的问题
* @Author:
* @param {object | NumberObj} first
* @param {object | NumberObj} second
* @return {number}
* @Date: 2023-08-08 14:25:06
*/
function addObj(first: object | NumberObj, second: object | NumberObj) {
// return first.count + second.count // 报错,因为first和second的类型是object,没有count属性
// instanceof 运算符的功能是检查对象的原型链是否包含特定类的原型,从而判断对象是否是该类的实例;之所以只能用于类(构造函数),是因为它是基于 JavaScript 中的原型链机制的。在 JavaScript 中,每个对象都有一个原型链,它链接到一个原型对象。当使用new关键字创建一个类的实例时,该实例会继承自该类的原型对象,从而形成一个原型链。
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count
}
return 0
}

泛型

泛型可以理解为一个占位符,用来表示类型,当我们调用函数时,再传入具体的类型

泛型函数

1
2
3
4
5
6
7
8
9
10
// 泛型函数
function getResult<T>(params: T, items: T): T {
if (typeof params === 'number') {
return params;
} else {
return items;
}
}
getResult<number>(123, 456); // number
getResult<string>('123', '456'); // string

泛型类

1
2
3
4
5
6
7
// 泛型类 可以在类的定义中使用泛型参数,以创建可以适用于多种数据类型的类
class GenericNiumber<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}

泛型接口

1
2
3
4
5
6
7
8
9
// 泛型接口
interface GenericIdentityFn<T, K> {
name: T;
age: K;
}

function identity(params: GenericIdentityFn<string, number>) {
return `${params.name} ${params.age}`
}

泛型约束

1
2
3
4
5
6
7
8
9
// 泛型约束
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}

使用泛型约束 T extends Lengthwise,表示 T 必须是满足 Lengthwise 接口要求的类型。

泛型约束中使用类型参数

1
2
3
4
5
6
7
// K extends keyof T 表示 K 是 T 的属性名称之一
// keyof T 会返回 T 的所有属性名称组成的联合类型。换句话说,它提取出类型 T 所有属性的键集合。
// K extends keyof T限制了参数 key 必须是对象 obj 的属性名称之一,从而确保在编译时能够正确访问对象的属性,并在使用不存在的属性时产生类型错误。

function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}

命名空间-namespace

命名空间的使用

TypeScript 的命名空间只对外暴露需要在外部访问的对象,命名空间内的对象通过 export 关键字对外暴露

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
/*
* @Description: namespace
* @Author: xiuji
* @Date: 2023-08-08 14:01:21
* @LastEditTime: 2023-08-10 15:39:20
* @LastEditors: Do not edit
*/

namespace Index {
class Header {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'this is header';
document.body.appendChild(elem);
}
}

class Content {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'this is content';
document.body.appendChild(elem);
}
}

class Footer {
constructor() {
const elem = document.createElement('div');
elem.innerText = 'this is footer';
document.body.appendChild(elem);
}
}

export class CreatePage {
constructor() {
new Header();
new Content();
new Footer();
}
}
}

外部通过new Index.CreatePage()调用

多文件的命名空间

像普通的 JS 模块文件可以相互引用一样,包含 namespace 的命名空间文件也可以相互引入,还可以组合成一个更大的命名空间,下面是一个简单的示例,所有文件都在同一目录下,你也可参考官方示例:

Utils.ts

1
2
3
4
5
6
namespace Utils {
export interface IAnimal {
name: string;
say(): void;
}
}

animal.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <reference path="utils.ts" /> ————通过这种注释表明关联什么命名空间

export namespace Animal {
export class Dog implements Utils.IAnimal{
name: string;
constructor(theName: string) {
this.name = theName;
}
say() {
console.log(`${this.name}: 汪汪汪`)
}
}
}

Index.ts

1
2
3
4
import {Animal} from './animal';

const he = new Animal.Dog('Jack');
he.say(); // Jack: 汪汪汪

将多个文件合并

修改tsconfig.json文件

1
2
"outFile": "./build",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"module": "amd", /* 多文件合并后不支持commonjs模式,仅支持'amd' and 'system' *