avatar

TypeScript学习笔记


tips

  • 传递数组或者对象作为函数参数时,可以用 Readonly<> 包裹原来的类型声明,譬如 Readonly<Array<number>>,TypeScript 编译器会通过拒绝编译来保护这个参数不被修改。如果的确需要一个可以被修改的数组,可以通过 spread 操作 [...array] 来复制这个数组

    • 1function sortNumbers(array: Readonly<Array<number>>) {
      2  return [...array].sort((a, b) => a - b);
      3}
      
  • 使用 unkown 而不是 any 来标注类型尚未明确的变量。Any 告诉 TypeScript 编译器,不需要检查类型;而 unkown 则是把检查留到使用变量的时候。使用时可以通过 typeof 运算符获取到变量的实际类型,通过 as 添加上类型后就可以正常使用了

  • 使用 Records 来代替 Objects,因为可以限定键的范围。如

    1type AllowedKeys = "name" | "age";
    2
    3// use a type here instead of interface
    4type Person = Record<AllowedKeys, unknown>;
    5
    6const Human: Person = {
    7  name: "Steve",
    8  age: 42,
    9};
    

tsconfig

隐式推断

ts 有隐式类型推断。开启后隐式 any 的推断会被检查并报错。

1// x被隐式推断成any,抛出错误
2function getNum(x) {
3  return x;
4}
5// problems: Parameter 'x' implicitly has an 'any' type.
1// 主动声明x是any避免错误
2function getNum(x: any) {
3  return x;
4}
1// 这种情况不会报错,因为隐式推断成undefined
2let y;
3console.log(y);
1// 这种情况也不会报错,因为赋值的时候类型推断成number
2let z;
3z = 123;
4console.log(z);

严格检查

默认情况 undefined 和 null 是其他类型的子类型,也就是其他类型变量可以被赋值 null 或 undefined。开启后会检查这种情况并报错。

1let num: number = undefined;
2// problems:Type 'undefined' is not assignable to type 'number'.

错误不输出

ts 默认即使编译报错也会生成 Js 文件,这一项设置 true,可以在报错的时候,不生成 js

基础

标记类型

  • boolean,number,string,null,undefined
  • void(一般用于函数空返回值,void 类型的变量可以赋值 undefined,不能赋值 null)
  • any(允许被赋值给任意类型,any 类型不会被类型检查)
  • unkonwn(允许被赋值给任意类型)

类型推论

定义时未指定类型且未赋值会被推断成 any,若赋值则会推断成赋值的类型

1let myFavoriteNumber = "seven"; //推断成string

联合类型

‘或’

1let myFavoriteNumber: string | number;

交叉类型

‘与’

 1interface A {
 2  a: string;
 3}
 4interface B {
 5  b: string;
 6}
 7let test: A & B = {
 8  a: "test a",
 9  b: "test b",
10};

对象的类型

描述对象的形状(shape)

1interface Person {
2  name: string;
3  age: number;
4}
5let tom: Person = {
6  name: "Tom",
7  age: 25,
8};

定义的变量比接口少一些属性或多一些属性都是不允许的

可选属性

1interface Person {
2  name: string;
3  age?: number;
4}
5
6let tom: Person = {
7  name: "Tom",
8};

任意属性

 1interface Person {
 2  name: string;
 3  age?: number;
 4  [propName: string]: any;
 5}
 6
 7let tom: Person = {
 8  name: "Tom",
 9  gender: "male",
10};

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

 1interface Person {
 2  name: string;
 3  age?: number;
 4  [propName: string]: string;
 5}
 6
 7let tom: Person = {
 8  name: "Tom",
 9  age: 25,
10  gender: "male",
11};
12
13// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
14// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
15//   Index signatures are incompatible.
16//     Type 'string | number' is not assignable to type 'string'.
17//       Type 'number' is not assignable to type 'string'.
18
19//上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

 1interface Person {
 2  name: string;
 3  age?: number;
 4  [propName: string]: string | number | undefined; // 因为age不一定有,可能是undefined
 5}
 6
 7let tom: Person = {
 8  name: "Tom",
 9  age: 25,
10  gender: "male",
11};

只读属性

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
简单例子

 1interface Person {
 2  readonly id: number;
 3  name: string;
 4  age?: number;
 5  [propName: string]: any;
 6}
 7
 8let tom: Person = {
 9  id: 89757,
10  name: "Tom",
11  gender: "male",
12};
13
14tom.id = 9527;
15
16// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
17// 上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。

注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:

 1interface Person {
 2  readonly id: number;
 3  name: string;
 4  age?: number;
 5  [propName: string]: any;
 6}
 7
 8let tom: Person = {
 9  name: "Tom",
10  gender: "male",
11};
12
13tom.id = 89757;
14
15// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
16//   Property 'id' is missing in type '{ name: string; gender: string; }'.
17// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
18//例中,报错信息有两处,第一处是在对 tom 进行赋值的时候,没有给 id 赋值。第二处是在给 tom.id 赋值的时候,由于它是只读属性,所以报错了。

数组的类型

类型+方括号表示法

1let fibonacci: number[] = [1, 2, 3, 4];

泛型表示法(常用)

1let fibonacci: Array<number> = [1, 2, 3, 4];

接口表示法(不常见)

1interface NumberArray {
2  [index: number]: number;
3}
4let fibonacci: NumberArray = [1, 2, 3, 4];

类数组的表示

类数组不是数组,不能用数组表示,但是可以参照接口表示法来写
比如函数参数 arguments。但事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等

 1function sum() {
 2  let args: {
 3    [index: number]: number;
 4    length: number;
 5    callee: Function;
 6  } = arguments;
 7}
 8//上述等价于
 9function sum() {
10  let args: IArguments = arguments;
11}
12//IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
13interface IArguments {
14  [index: number]: any;
15  length: number;
16  callee: Function;
17}

函数的类型

函数声明式(简单,推荐)

1function sum(x: number, y: number): number {
2  return x + y;
3}

函数表达式

 1let mySum: (x: number, y: number) => number = (
 2  x: number,
 3  y: number
 4): number => {
 5  return x + y;
 6};
 7// 第一个冒号到第一个等号之间的可以省略,相当于不写type
 8//ts会类型推断的,不然这么写
 9//就像写了两遍
10
11//或者这么写,注意是Function不是function,其实也相当于少写了type
12let newSum: Function = (x: number, y: number): number => {
13  return x + y;
14};

用接口定义函数,注意参数的括号

1interface SearchFunc {
2  (source: string, subString: string): boolean;
3}
4let mySearch: SearchFunc = (source: string, subString: string): boolean => {
5  return source.search(subString) !== -1;
6};

可选参数(可选参数要放在最后),参数默认值

1function buildName(firstName?: string, lastName: string = "Clark"): string {
2  if (firstName) {
3    return firstName + lastName;
4  }
5  return lastName;
6}

剩余参数,...rest 访问剩余参数

1function myPush(array: Array<unknown>, ...rest: Array<unknown>) {
2  rest.forEach((item) => {
3    array.push(item);
4  });
5}
6myPush([], 1, 2, 3);

重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'

1function reverse(x: number | string): number | string | void {
2  if (typeof x === "number") {
3    return Number(x.toString().split("").reverse().join(""));
4  } else if (typeof x === "string") {
5    return x.split("").reverse().join("");
6  }
7}

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串
这时,我们可以使用重载定义多个 reverse 的函数类型:

 1function reverse(x: number): number;
 2function reverse(x: string): string;
 3function reverse(x: number | string): number | string | void {
 4  if (typeof x === "number") {
 5    return Number(x.toString().split("").reverse().join(""));
 6  } else if (typeof x === "string") {
 7    return x.split("").reverse().join("");
 8  }
 9}
10//上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
11
12//注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

类型断言

语法

  • 值 as 类型(推荐)
  • <类型>值 (不推荐,tsx 语法中<Foo>表示 ReactNode;而在 ts 中泛型也是这种语法,容易混淆)
  • 作用:给一个类型断言,让 ts 编译器听你的,若使用不当会造成运行时错误。常用于 ts 的一些不好解决的报错
  • 示例
 1interface Cat {
 2  name: string;
 3  run(): void;
 4}
 5interface Fihs {
 6  name: string;
 7  swim(): void;
 8}
 9function isFish(animal: Cat | Fish) {
10  if (typeof (animal as Fish).swim) {
11    return true;
12  }
13  return false;
14}
1(window as any).foo = 1;
 1function getCacheData(key: string): any {
 2  return (window as any).cache[key];
 3}
 4
 5interface Cat {
 6  name: string;
 7  run(): void;
 8}
 9
10const tom = getCacheData("tom") as Cat;
11tom.run();
 1//用泛型可以更好地实现上述代码
 2function getCacheData<T>(key: string): T {
 3  return (window as any).cache[key];
 4}
 5
 6interface Cat {
 7  name: string;
 8  run(): void;
 9}
10
11const tom = getCacheData<Cat>("tom");
12tom.run();

声明文件

评论列表:

暂无评论 😭