TS

272 阅读13分钟

首先先来看看TS能限制什么,不能限制什么:

  • 定义外部返回值(接口io,文件io)的格式来限制真实返回
  • 访问general object上的xjb属性,比如arr[].name (因为general object没有固定scheme)
  • 访问定义好格式对象上的不存在属性
  • 给定义好格式的对象直接通过字面量乱赋值 (const u: User = { name: 'TS', age: 8 }必须符合User格式)
  • 给定义好格式的对象通过变量传递赋值,不符合的属性会被忽略(const obj={name,age,sex} const u: User = obj,不报错,sex会被忽略)
  • 按预定格式(type|interface)实现类和方法,实现和使用都必须和格式一致

1. 数据类型

TS的数据类型 (蓝色是JS中的,黄色为TS新加的)

image.png

基本类型

  • 对于基本类型,let a:number = 6 let a = 6等效, const a:number = 6 const a = 6不等效

image.png

在let变量中省略会触发literal widening,字面量类型的拓宽

对于某些类型,ts也存在widening和narrowing

image.png

image.png

引用类型

数组(Array)

image.png

image.png

如果数组内元素个数固定,类型不同,可以用

元组(tuple)

const x: [number, string, State?] = [12, 'age', xxx]

大写的State为自定义类型,问号表示可选元素,需放在所有必选后面

image.png

image.png

object(包含Object{})

image.png

function

该类型会在第三节单独介绍

特殊类型

any

image.png any, unknown类型前者可以赋或被赋任何值,后者只能被赋,且相关操作受限

unknown

image.png void

当一个函数没有返回值,其即为void类型,我们不能把 void 类型的变量值再赋值给除了 any 和 unkown 之外的任何类型变量。

undefined,null:

null 和 undefined 两个类型一旦赋值上,就不能在赋值给任何其他类型 image.png

never

表示一个函数永远不会有返回值, void会返回undefined, never连undefined也不行

1.  function InfiniteLoop(): never {
        while (true) {}
     }

(圆括号后 : + 类型注解 表示函数返回值的类型)

never 是所有类型的子类型,它可以给所有类型赋值,但是反过来,除了 never 自身以外,其他类型(包括 any 在内的类型)都不能为 never 类型赋值

image.png

Enum(枚举类型)

TypeScript 支持数字、字符串两种枚举类型,定义的名称不能为关键字,和字面量类型某些情况等效:

1.  type Day = 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY';

等效于

1.  enum Day {
1.  SUNDAY,
1.  MONDAY,
1.  TUESDAY,
1.  WEDNESDAY,
1.  THURSDAY,
1.  FRIDAY,
1.  SATURDAY
1.  }

1. 数字枚举 枚举的类型默认为数字类型 image.png

  enum Day {
  SUNDAY = 1,
  MONDAY,
  TUESDAY,
  WEDNESDAY,
  THURSDAY,
  FRIDAY,
  SATURDAY
  }

在上述示例中,我们指定了从 1 开始递增。

2. 字符串枚举

字符串枚举要注意的是必须要有默认值,不支持反向映射

1.  enum Day {
1.  SUNDAY,
1.  MONDAY,
1.  TUESDAY,
1.  WEDNESDAY,
1.  THURSDAY,
1.  FRIDAY,
1.  SATURDAY
1.  }

3. 常量枚举

image.png

4. 异构枚举

image.png

哪些情况需要枚举而不是字面量类型?

image.png

image.png

image.png

类型断言

image.png 比如下面例子,如果不能find大于2的数则会返回undefined赋值给number类型的变量(greaterThan2),这是ts不允许的

image.png

断言扩展

非空断言

image.png str会帮str1把null和undefined挡掉

确定赋值断言

image.png

双重断言

image.png

类型守卫

类型守卫:是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内

其实更准确的叫法应该是类型缩窄(narrowing)

目前,常有的类型守卫共有4种:in关键字typeof关键字instanceof类型谓词(is)

  • in关键字 (类似的有可产生联合类型的keyof)

image.png

  • typeof

image.png

  • instanceof

image.png

  • 类型谓词is

image.png

类型守卫 vs 类型断言

image.png

类型推断

image.png

一般情况下,TypeScript 中的函数返回值类型是可以缺省和推断出来的,但是有些特例需要我们显式声明返回值类型,比如 Generator 函数的返回值:

image.png

高级类型

高级类型其实就是把上述各种类型进行组合、占位、复用、具象表达等技巧衍生出的手段

字面量类型 (Literal Type)

image.png

字面量类型是常规类型的子集,比如"this is string"类型一定是string类型,反过来不一定, true类型一定是boolean类型,反过来不一定

字面量类型可以限制函数的参数为指定的字面量类型集合

// 定义一个函数,它只接受特定的几种状态字符串
type Status = 'pending' | 'success' | 'failed';

function handleStatus(status: Status) {
  // ...
}

// --- 使用 const ---
const currentStatus = 'pending'; // ✅ 类型被推断为字面量 'pending'
handleStatus(currentStatus);     // ✅ 正确!'pending' 可以赋值给 Status 类型

// --- 使用 let ---
let nextStatus = 'pending';      // ⚠️ 类型被拓宽为 string
handleStatus(nextStatus);        // ❌ 错误!类型 'string' 不能赋值给类型 'Status'
                                 // 因为 string 类型太宽泛了,它可能是 "any_other_string"

交叉类型 (Intersection Type)

image.png

image.png

  • 同名基础类型交叉(交叉内容为string和number的同名属性,交集无)

image.png

  • 同名非基础类型交叉(交叉内容为{a:string}和{b:number}的同名属性,交集{a:string,b:number})

image.png

联合类型 (Union Type)

image.png

普通类型的交叉相对于求并集: image.png

如果是联合类型的交叉,则相当于求交集:

image.png image.png

image.png 最后一行报错因为Pet可能是鸟或者鱼,所以不一定有fly方法

可辨识联合

image.png 在setInfo里面可以通过type来辨识data是满足哪个interface进来的

类型别名:也就是type,用来给一个类型起个新名字

    type InfoProps = string | number
    
    const setInfo = (data: InfoProps) => {}

泛型 (Generics Types)

是指在定义函数、接口或类的时候,故意不预先指定具体的类型,而在使用的时候再指定类型的一种特性

也就是说,泛型是允许同一个函数接受不同类型参数pattern的一种模版,与any相比,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型(PS:泛型是整个TS的重点,也是难点,请多多注意~)

泛型指的是类型参数化,即将原来某种具体的类型进行参数化。和定义函数参数一样,我们可以给泛型定义若干个类型参数,并在调用时给泛型传入明确的类型参数。设计泛型的目的在于有效约束类型成员之间的关系,比如函数参数和返回值、类或者接口成员和方法之间的关系。

用泛型来约束函数入参

我们可以通过尖括号 <> 语法给函数定义一个泛型参数 laozi,并指定 param 参数的类型为 laozi ,如下代码所示,可以理解为laozi只是一个占位符,

1.  function reflect<laozi>(param: laozi) {
2.  return param;
3.  }

image.png 我们可以给函数定义任何个数的泛型入参,如下代码所示:

1.  function reflectExtraParams<P, Q>(p1: P, p2: Q): [P, Q] {
2.  return [p1, p2];
3.  }

//也可以用来约束箭头函数
const a = <P>(param: P) => P;

用泛型来约束类

image.png

这种省略只是凑巧构造函数传入类型刚好是< S>,如果class Memory< S> 但内部constructor(name:string),这种时候就不能省略 new Memory('tom')了

真正肯定省略的情况就是给泛型默认值

用泛型来约束type/interface(契约化)

1.  type ReflectFuncton<P> = (param: P) => P;
2.  interface IReflectFuncton<P> {
3.      (param: P): P
4.  }

通过落地泛型来将契约兑现

    const reflectFn2: ReflectFuncton<string> = 一个接受字符串的函数
    const reflectFn3: IReflectFuncton<number> =一个接受数字的函数

用泛型来约束容器,内置对象

各种容器类Map、Array、Set、Promise等;各种组件,比如React.Component。 eg. new Map<string,number>(); 约束了map的键值对类型 Promise<void>; 约束了Promise的resolve类型

默认泛型和窄化泛型

如何设置默认泛型

image.png 默认值在泛型用于函数参数时其实没什么用,函数该怎么用怎么用,因为函数调用必须传具体值,但在传虚的类型场景下有用,比如约束类,约束type/interface

如何窄化泛型的范围

我们希望把接收参数的类型限定在几种原始类型的集合中,此时就可以使用“泛型入参名 extends 类型”语法达到这个目的,这时extends 表达可分配给的意思 如下代码所示:

1.  function reflectSpecified<P extends number | string | boolean>(param: P):P {
2.  return param;
3.  }
4.  reflectSpecified('string'); // ok
5.  reflectSpecified(1); // ok
6.  reflectSpecified(true); // ok
7.  reflectSpecified(null); // ts(2345) 'null' 不能赋予类型 'number | string | boolean'
Extends

既然说到extends和可分配,来看一下extends不同语境下的意思和可分配不同语境的意思

  • 首先extend可以表示类继承和interface继承
  • 然后T extends 'light'|'dark'表示泛型T必须可分配给xxx类型(这里的可分配是子集)
  • 然后let res = moreType extends LessType ? 'Yes' : 'No';这里的extends 代表“A 是否可以分配给 B?” (这里的可分配是超集)

image.png

工具类型 (Utility Types)

其实也是通过泛型落地来生成新类型

Partial<T>

image.png

Required<T>

image.png

Readonly<T>

image.png

Pick<T,K>

image.png (上图说的属性字面量就是对象的键,因此T不能是字面量或其联合类型,因为那样没有键)

Omit<T,K>

image.png

Extract<T,U>

image.png

Exclude<T,U>

image.png

Record<K,T>

image.png 就是构建一个对象,key遵循K规范,值遵循T规范。

image.png

NonNullable<T>

image.png

2. 接口

1. 接口可以用来定义函数的参数类型:

image.png

image.png

2. 接口可以用来定义函数的类型:

image.png

当Interface中定义了匿名函数,那符合该interface规范的变量就得是个函数

3. 接口可以用来描述对象形状,约束变量

let TypeScript: ProgramLanguage;

这样后续在给TypeScript这个变量赋值的时候,就必须要符合上面的接口规范

image.png

4. 接口字段个数不确定时,可以使用索引签名来约束

image.png 用任意的number类型去索引Rank类型的变量,都会得到string类型的结果, Year反之。 接口定义索引类型为string,即使传入数字也可以兼容 (1:1970),换句话说,可以同时被string和num来索引,但得到的结果必须是同类型或string索引对应类型更广大

因此不可以如下截然相反的定义: image.png

泛型接口

如果接口被泛型填充,代表一个可以被复用的契约

interface Store<T>{ state:T}

//如果泛型有默认值,则可以直接当做一个能直接用的约束

interface Store2<T = { id: number; name: string }> {
  state: T
}

type newStore = Store2;

接口继承

接口继承接口

image.png

接口继承类

「接口继承类」和「接口继承type」没有什么本质的区别

image.png 实际上还是继承Point type而不是Point Class 值得注意的是,Point Type 相比于 Point Class,缺少了 constructor 方法,这是因为声明 Point 类时创建的 Point 类型是不包含构造函数的。另外,除了构造函数是不包含的,静态属性或静态方法也是不包含的(实例的类型当然不应该包括构造函数、静态属性或静态方法)。

换句话说,声明 Point 类时创建的 Point 类型只包含其中的实例属性和实例方法:

image.png

Interface 与 Type 的区别

通过上面的学习,我们发现类型别名接口非常相似,可以说在大多数情况下,typeinterface是等价的

但在一些特定的场景差距还是比较大的,接下来逐个来看看

  • 基础数据类型

image.png

  • 扩展

image.png

  • 重复定义

image.png

3. 函数

  • 有两种方式,一种为 function, 另一种为箭头函数
  • 在书写的时候,也可以写入返回值的类型,如果写入,则必须要有对应类型的返回值,但通常情况下是省略,因为TS的类型推断功能够正确推断出返回值类型

声明参数及返回值

image.png (add和del的两种声明方式等价)

使用void表示没有返回值的函数:

image.png 可以在参数首位声明this指向,而不用像js在运行函数时才确定

image.png 当执行时发现this没有指向规定值时会报错

参数类型

image.png image.png

返回值类型

第一种定义方法:

function test (arg:number):void{
  console.log(arg)
}

第二种定义方法: const test = (a: number, b: number): number => {     return a + b; }

函数重载

函数重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 在 TypeScript 中,表现为给同一个函数提供多个函数类型定义。 简单的说:可以在同一个函数下定义多种类型值,最后汇总到一块 image.png

注意:ts会按列表顺序匹配第一个符合的条目

4. 类(Class)

定义类中的属性有三种方式,一是顶层(可省略修饰符),而是构造函数参数(不可省略修饰符),三是混合 image.png

访问修饰符

  • public:类中、子类内的任何地方、外部都能调用
  • protected:类中、子类内的任何地方都能调用,但外部不能调用
  • private:类中可以调用,子类内的任何地方、外部均不可调用

image.png

私有字段

image.png

只读属性

只读属性:用 readonly修饰,只能在构造函数中初始化,并且在TS中,只允许将interfacetypeclass上的属性标识为readonly

  • readonly实际上只是在编译阶段进行代码检查
  • radonly修饰的词只能在 constructor阶段修改,其他时刻不允许修改

image.png

存取器

getter, setter 会拦截对与其名称同名的属性,并接入相应逻辑 image.png

抽象类

image.png 在第 1~10 行,通过 abstract 关键字,我们定义了一个抽象类 Adder,并通过abstract关键字定义了抽象属性xy及方法add,而且任何继承 Adder 的派生类都需要实现这些抽象属性和方法。

同时,我们还在抽象类 Adder 中定义了可以被派生类继承的非抽象属性displayName和方法addTwice。 然后,我们在第 12~23 行定义了继承抽象类的派生类 NumAdder, 并实现了抽象类里定义的 x、y 抽象属性和 add 抽象方法。如果派生类中缺少对 x、y、add 这三者中任意一个抽象成员的实现,那么第 12 行就会提示一个 ts(2515) 错误,关于这点你可以亲自验证一下。

重写和重载

image.png

类的类型

image.png 简言之,抽象类中的抽象属性需要子类内部自己实现,非抽象方法可以直接调用

类的extends和implements

实现

实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。

image.png

image.png

继承

image.png