# 2022-07 TypeScript

# 一、TypeScript 断言

# 1、类型断言

型断言主要用于当 TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式:

// 一是“尖括号”语法:
let someValue1: any = "this is a string";
let strLength1: number = (<string>someValue1).length;

// 另一个为as语法:
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;
1
2
3
4
5
6
7

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as 语法断言是被允许的。

# 2、非空断言

在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。

# 1.赋值时忽略 undefined 和 null 类型

function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'. 
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
1
2
3
4
5
6

# 2.调用函数时忽略 undefined 类型

type NumGenerator = () => number;

function myFunc(numGenerator: NumGenerator | undefined) {
  // Object is possibly 'undefined'.(2532)
  // Cannot invoke an object which is possibly 'undefined'.(2722)
  const num1 = numGenerator(); // Error
  const num2 = numGenerator!(); //OK
}
1
2
3
4
5
6
7
8

! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要注意手动处理空值

ts 代码:

const a: number | undefined = undefined;
const b: number = a!;
console.log(b); 
1
2
3

编译后的 js 代码:

"use strict";
const a = undefined;
const b = a;
console.log(b);
1
2
3
4

执行上述代码,控制台会输出 undefine。直接使用变量 b 就需要考虑 undefine 的情况。

const c = 1;
console.log(b+c) // NaN
1
2

# 3、确定赋值断言

在 TypeScript 2.7 版本中引入了确定赋值断言,即允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值。

let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454)
console.log(2 * x); // Error

function initialize() {
  x = 10;
}
1
2
3
4
5
6
7
8

很明显该异常信息是说变量 x 在赋值前被使用了,要解决该问题,我们可以使用确定赋值断言:

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}
1
2
3
4
5
6
7

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。或者在 class 里面定义变量的时候,没有赋初始值时使用。

# 二、TypeScript 拓展 Window 全局变量

TypeScript 里面使用 window 对象上的变量会报错 Property 'example' does not exist on type 'Window & typeof globalThis'.(2339)

// ⛔ Property 'example' does not exist on type 'Window & typeof globalThis'.(2339)
window.example = 'hello';

console.log(window.example);
1
2
3
4

创建类型定义文件 src/types/index.d.ts

# 1. 方案一

export {};

declare global {
  interface Window {
    example: string;
  }
}
1
2
3
4
5
6
7

export {} 主要是为了让这个文件变成一个外部模块。模块至少包含1个 import 或 export 语句,才能作用到全局作用域。

# 2.方案二

declare module 'global-config' {
  global {
    interface Window {
      example: string;
    }
  }
}
1
2
3
4
5
6
7

# 3.方案三

直接定义 Window 就可以,不知道有什么弊端没有,搜索到的大都是上面两种方法。

interface Window {
  example: string;
}
1
2
3

# 三、ESLint 检查 ts 文件

需要在 .eslintrc.js 文件里增加 parse 配置

module.exports = {
  parserOptions: {
    parser: '@typescript-eslint/parser',
  },
}
1
2
3
4
5

# 四、类型谓词 is

TypeScript 中的 is 关键字,它被称为类型谓词,用来判断一个变量属于某个接口或类型。

// 接口 interfaceA
interface interfaceA {
  name: string;
  age: number;
}

// 接口 interfaceB
interface interfaceB {
  name: string;
  phone: number;
}

// 推断类型
const obj1 = { name: "andy", age: 2 };
// const obj1: {name: string;age: number;}

const obj2 = { name: "andy", phone: 2 };
// const obj2: {name: string;phone: number;}

// 创建数组
// arr1, 创建两个interfaceA[]数组, 数组每一项都是 obj1
const arr1 = new Array<interfaceA>(2).fill(obj1);
// const arr1: interfaceA[]

// arr2, 创建两个interfaceB[]数组, 数组每一项都是 obj2
const arr2 = new Array<interfaceB>(2).fill(obj2);
// const arr2: interfaceB[]

// 合并两种类型数组,
//  arr3类型就是一个联合数组
const arr3 = [...arr1, ...arr2];
// const arr3: (interfaceA | interfaceB)[]


const target = arr3[0];
// const target: interfaceA | interfaceB  

// Ok获取两个结构共有的属性
console.log(target.name);

// 获取两个接口不同的属性报错:
console.log(target.phone);
// 报错: 类型“interfaceA”上不存在属性“phone”

console.log(target.age);
// 报错:  类型“interfaceB”上不存在属性“age”


// 联合类型
type interfaceAB = interfaceA | interfaceB;

// 自定义类型保护函数
const isInterfaceA = (item: interfaceAB): item is interfaceA => {
  return (item as interfaceA).age !== undefined;
};

// 判断target 属于哪个类型
if (isInterfaceA(target)) {
  console.log(target.age); //target的类型为interfaceA
} else {
  console.log(target.phone); //target的类型为interfaceB
}
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

# 通过泛型定义通用的类型保护函数

function isOfType<T>(target: unknown, key: keyof T): target is T {
  return (target as T)[key] !== undefined
}
1
2
3

# 五、TypeScript 高级类型

用法 描述
& 交叉类型,将多个类型合并为一个类型,交集
' '
T extends U 类型约束,判断 T 是否可以赋值给 U
P in T 类型映射,遍历 T 的所有类型
parameterName is Type 类型谓词,判断函数参数 parameterName 是否是 Type 类型
infer P 待推断类型,使用 infer 标记类型 P,就可以使用待推断的类型 P
typeof v === "typename" 原始类型保护,判断数据的类型是否是某个原始类型(number、string、boolean、symbol)
instanceof v 类型保护,判断数据的类型是否是构造函数的 prototype 属性类型
keyof 索引类型查询操作符,返回类型上已知的 公共属性名
T[K] 索引访问操作符,返回 T 对应属性 P 的类型

# 六、TypeScript 工具泛型

用法 描述
Readonly 将 T 中所有属性都变为只读
ReadonlyArray 返回一个 T 类型的只读数组
ReadonlyMap<T, U> 返回一个 T 和 U 类型组成的只读 Map
Partial 将 T 中所有的属性都变成可选类型
Required 将 T 中所有的属性都变成必选类型
Pick<T, K extends keyof T> 从 T 中摘取部分属性
Omit<T, K extends keyof T> 从 T 中排除部分属性
Exclude<T, U> 从 T 中剔除可以赋值给 U 的类型
Extract<T, U> 提取 T 中可以赋值给 U 的类型
Record<K, T> 返回属性名为 K,属性值为 T 的类型
NonNullable 从 T 中剔除 null 和 undefined
ConstructorParameters 获取 T 的构造函数参数类型组成的元组
InstanceType 获取 T 的实例类型
Parameters 获取函数参数类型组成的元组
ReturnType 获取函数返回值类型

# 七、Markdown table 输入竖线 "|"

直接输入 | 之类是不行的。 markdown 竖线

需要转义,用 &#124; 或者 &#x7C; 来代替。 markdown 竖线

上次更新: 8/1/2022, 3:49:46 PM