关于前端:TypeScript类型标注相关概念一览

3次阅读

共计 9026 个字符,预计需要花费 23 分钟才能阅读完成。

背景

TypeScript 重构了一遍业务后盾,TS 很大一部分在解决类型标注,根底的类型很容易上手,但到泛型一块,形象水平一下子就高了起来,提供的许多工具也简单了起来。重构的时候始终想整顿一份不便查问的笔记始终没空,当初总算抽出工夫整顿了一份,将于类型无关的局部整顿了进去,其余局部还须要自行翻阅相干文档。
笔记相似纲要,晓得什么状况下有什么能够用,有一个范畴概念,具体细节通过笔记提供的关键字再搜寻相干文档认真理解。

根底类型

TS 是 JS 的超集,所以 JS 根底的类型都蕴含在内:
booleannumberstringsymbolarray([])、object({})、nullundefined

TS 还提供了其余更加具体的类型标识:
any:任意类型,TS 将不对其进行类型检测
unknown:未知类型,在晚期对于未知类型咱们个别标注any,但标注any 将不会对类型进行检测,unknown则在对变量第一次赋值后,除了 any 类型,其余类型禁止再赋值给此变量,同时在应用其类型所对应的办法时也须要先膨胀类型,能力失常的应用
void:无返回,当一个函数没有返回值的时候此类型标识
never:当函数永远没有返回值的时候用此类型标识。如始终抛出异样的函数,其返回值就是never
tuple:元组,数组的更准确的标识办法,能够限定数组的数量与每个位的类型
enum:枚举,能够给每一个预约值的数值或是字符串提供扼要的名称,不便编写时辨别
... | ...:类型字面量,如'Red' | 'Green' | 'Blue',此时这变量就只能赋值这三种字符串,如果赋值其余字符串时将会报错,也能够是数字字面量 1 | 2 | 3

其余
  • 默认状况下 nullundefined是所有类型的子类型,能够将这两个值赋值给任意类型。
  • never类型也是任何类型的子类型。但其余类型无奈赋值给 never 类型,只有 never 能力赋值给 never 类型。
  • objectObject,这两个正文类型只有一个大小写之分,用Object 申明的类型能够拜访 Object 对象 的默认办法,但 object 则不行。

基本概念

: (冒号标注)

TS 有主动类型推断,当赋上初始值后 TS 会生成相应的类型标注,所以个别状况下不用手动增加

let variate: boolean = true
let variate: number = 1
let variate: string = 'one'
let variate: number[] = [1,3,4,6] // 数组
let variate: {first: string, second: number} = {first: 'one', second: 2} // 对象
let sym1 = Symbol('key') // symbol
let variate: number[string, number] = ['one', 2] // 元组
enum color {Red, Green, Blue} // 枚举

函数参数的类型标注

function func(params: {
  first: string;
  second: number;
}): void {}
const func = function(params: {
  first: string;
  second: number;
}): void {}

函数的类型标注

const func: (first: string, second: number) => void = function(params) {}
const func: (first: string, second: number) => void = () => {}  // 这种写法很不容易分辨,只有记录函数的类型标注是必须显示指定返回值的,所以当看到返回值的标注才是类型标注齐全完结

interface (接口)

此是最罕用的类型标注形式。
如上例变量标注也能够用接口代替:

interface IVariate {
  first: string;
  second: number;
}
let variate: IVariate = {first: 'one', second: 2}

其余个性

? 可选标识符

interface IVariate {
  first: string;
  second?: number;
}

readonly 只读标识符

interface IVariate {
  first: string;
  readonly second: number;
}

[key: string | number]: any 任意数量标识

有时候类型数量是动静的,但类型是指定须要束缚的,能够这样写

interface IVariate {
  first: string;
  readonly second: number;
  [key: string]: number;
}

extends 接口的继承

interface IVariate {
  first: string;
  second: number;
}
interface IVariate2 extends IVariate {third: string}
等价于:
interface IVariate2 {
  first: string;
  second: number;
  third: string;
}

利用 , 能够进行多继承

interface IVariate3 extends IVariate, IVariate2 {fourth: string}

类型合并

interface IVariate {
  first: string;
  second: number;
}
interface IVariate {third: number;}

反复申明类型默认会将申明合并起来,如果有反复定义的类型标注,前面的会把后面的 笼罩

函数

函数参数 的类型标注

interface IParams {
  first: string;
  second: number;
}
function func(params: IParams) {}
const func = function(params: IParams) {}

函数 的类型标注

残缺的标注函数参数以及函数返回值,与参数类型标注一样,只是将定义挪动到 interface 中。

interface IFunc {(first: string, second: number): void  // 这里须要留神,在 interface 中函数的类型标注是冒号 (:) 而不是箭头(=>)
}
const func: IFunc = function(params) {}
const func: IFunc = () => {}

不定参数的类型标注

const func = function(...params: any[]) {}

可选参数的类型标注

须要留神,可选类型之后不能有必选类型。

const func = function(first: string, second?: number) {}

不定参数的类型标注

const func = function(...params: any[]) {}

this类型标注

因为 JS 的 this 是动静指向的,所以 this 类型默认标注为any,如果须要显式标注能够在函数第一个参数位进行标注

const func = function(this: void, first: string, second?: number) {}

函数重载

当传入不同参数须要返回不同类型时,此时能够用函数重载来标识

function func(data: number): number
function func(data: string): string
function func(data: number | string): string | number { // 必须要有一个函数反对所有类型
    if(typeof data === 'number') return data+1
    return data
}

这一块跟 C# 很像,根底的元素都搬过去了,能够间接应用。

类的申明

class People {
    name: string;
    constructor(name: string) {this.name = name;}
    hello() {return `Hello ${this.name}`;
    }
}

类的继承

class Student extends People {
    number: number = 0
    constructor(name: string, number: number) {super(name);
        this.number = number
    }
    hello() {return `Hello ${this.name}, Number ${this.number}`;
    }
}

修饰符

修饰符与其余语言一样,publicprivateprotected。不表明时默认为 public
动态属性static,只读readonly,可选?

存取器getset

class People2 {
    private _name: string;
    constructor(name: string) {this.name = name;}
    get name(): string {return this._name}
    set name(newName: string) {this._name = `name: ${newName}`
    }
}

abstract抽象类 形象办法

标识 abstract 关键词,类与办法都不用在定义时被实现,必须在继承的类中实现具体方法

abstract class People3 {abstract hello() {}}

implements类的接口继承

类的继承只能是单继承,没方法多继承,但理论开发中经常会有一个性能须要多个类应用,这时候能够用接口

interface IStudy {gender: string}
interface IGrade {grade(): string
}
class Student2 extends People implements IStudy, IGrade // implements 能够用逗号分隔指定多个
{
    number: number = 0
    gender: string
    constructor(name: string, number: number, gender: string) {super(name);
        this.number = number
        this.gender = gender
    }
    hello() {return `Hello ${this.name}, Number ${this.number}`;
    }
    grade(){return 'A'}
}

| 联结类型

通常状况下一个变量一个类型是不够用的,所以会须要指定多种类型,这时候就须要用到联结类型符号

let variate: number | string  // 这样就能反对 number 类型与 string 类型

& 穿插类型

有时候新的类型是两个旧类型的并集,这时候能够用穿插类型运算符生成新的类型,而不用从新申明

let variate: IVariate & {data: string}  // 会生成新的类型,其中蕴含 first second data 三个 key

as<>类型断言

<>断言写在后面 <string>variate,但因为与JSX 语法会有抵触,所以个别应用 as 语法,as语法写在前面 variate as string
有的场景咱们要应用一个类型的外部值,但 TS 又会报错时,这时候能够间接强制将类型膨胀。
某些条件下内部传入的值始终合乎预期无需判断,这时候咱们也能够应用断言。

interface IA {
  first: string;
  second: number;
}
interface IB {
  name: string;
  age: number;
}
function func(data: IA | IB) {if((data as IB).name) // ... 省略
    else return // ... 省略
}

function func2(data: IA) {(data as any as IB).name  // 当本来没有标注类型 IB 时,无奈间接断言,能够应用两次断言强制指定一个类型
}

is关键字

is能够将类型膨胀成某一类型

function isNumber(x: any): x is number {return typeof x === 'number'}

!关键字

某些场景类型定义变量是蕴含 undefined 的,但咱们应用的时候确定这时候无需判断,能够应用 ! 断言来打消正告提醒。

interface IVariate {
  first: string;
  second: number;
}
let variate: IVariate | undefined
let second = variate!.second

type 类型别名

类型别名与 interface 一样,但比 interface 更加弱小通用。
比方类型字面量想要从新起一个名称方便使用

type color = 'Red' | 'Green' | 'Blue'

甚至能够间接给原始类型起别名

type name = string

或是给 interface 再起别名

type funcParams = IParams

也能够应用&|

type funcParams2 = IParams & IParams2
type funcParams2 = IParams | IParams2

泛型

此前的根底概念大多数相熟面向对象编程语言的能够很快就上手,但到泛型一块,概念性的货色是多了起来,语法也越渐变得复杂起来,TS 制订了一套语法,除了写业务逻辑,咱们还须要对 JS 类型进行编程。

为什么会有泛型呢?在个别应用中,类型绝对固定,是可预期的。但如果要写通用组件时,咱们没方法齐全预期传入的类型和返回类型,有一些返回类型可能是须要用户自定义的,这时候就须要用到泛型来让用户在内部标注类型。

<T>泛型符号

跟断言很像,但括号这里是在应用时传入的定义类型,命名为 T,其中符号能够随便自定义,但有一些罕用的关键字:T 为 type,U为 T 的下一位,K为 key,V为 Value,N为 Number,E为 Element
应用时相似这样:

function func<T, U>(arg1: T, arg2: U): [T, U] {return [arg1, arg2]
}
const <T, U>func = (arg1: T, arg2: U) => [arg1, arg2]

class People<T> {
    name: string;
    data: T
}

// 接口中应用泛型
interface IArg<T, U> {
  arg1: T;
  arg2: U;
}

根底泛型工具

keyof获取键名工具

要解决的类型往往是一个汇合,所以须要有一个工具能够获取汇合中的键名、键值

interface IParams {
  first: string;
  second: number;
}

// 获取键名
type keyList = keyof IParams; // "first" | "second"
// 获取键值
type keyList = IParams[keyof IParams]; // string | number

in映射工具

in能够将 keyof 每一次循环出的值映射给新的变量。
比方咱们遇到了一个新场景,同样是应用 IParams 类型,但其中所有参数是可选的,并非默认必选的,这时候咱们新建一个反复的类型就很麻烦,能够应用 in keyof 来将旧类型转换为新类型

// 能够应用 type 构建咱们解决工具 Partial
type Partial<T> = {[P in keyof T]?: T[P]
}
// 当做泛型,将定义的类型传入
type IParamsPartial = Partial<IParams> // 是不是有类型编程的滋味了

// 去除可选可用 - 号标识
type Required<T> = {[P in keyof T]-?: T[P]
}
type IParamsRequired = Required<IParamsPartial>

extends继承

同样的逻辑,能够用来束缚泛型的格局。用U extends T U 继承自 T,具备 T 中的定义,所以泛型传入的参数必须实现 T 中的定义

// 咱们束缚传入的值必须带 name 属性 并且值类型为 sting
interface IArg {name: string}
function func<T extends IArg>(arg: T){return arg}

// 应用
func<{age: number}>({age: 1}) // 当传入泛型的类型不合乎束缚时会提醒谬误  Type '{age: number;}' does not satisfy the constraint 'IArg'. Property 'name' is missing in type '{age: number;}' but required in type 'IArg'

func<{name: string, age: number}>({name: 'name', age: 1})  // 只有泛型类型加上指定的 name: string 时才会失常

能够与 keyof 组合应用。
比方咱们创立一个函数,第一个参数传入一个对象,第二个参数传入对象的键名,返回此键名对应的值。此时键名参数就是动静的了,写 any 无奈达到类型检测的目标,能够应用 extends keyof 来进行束缚

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

const lsit = {a: 1, b: 2, c: 3}
getObjectValue(lsit, 'a'); // 通过 返回 1
getObjectValue(lsit, 'e'); // 报错 Argument of type '"e"' is not assignable to parameter of type '"a" | "b" | "c"'.

infer待推断变量工具

infer必须与 extends 联合应用,语句格局 T extends (infer U)?true : false(infer U) 局部就是咱们填写将要匹配的类型推断主体,类型 T 满足类型 U,执行true 中的逻辑,否则执行 false 中的逻辑。
手册的例子就很好的展现了多种类型的推断匹配:

type Unpacked<T> =
    T extends (infer U)[] ? U : // 如果传入的是数组 则将数组的类型命名为 U 并且返回 U 类型
    T extends (...args: any[]) => infer U ? U :  // 如果传入的是函数 则将函数的返回值命名为 U 并且返回 U 类型
    T extends Promise<infer U> ? U :  // 如果传入的是 Promise 则将 Promise 的泛型参数命名为 U 并且返回 U 类型
    T;  // 如果所有皆否 则返回 T 类型
    
type T0 = Unpacked<string>;  // string
type T1 = Unpacked<string[]>;  // string
type T2 = Unpacked<() => string>;  // string
type T3 = Unpacked<Promise<string>>;  // string
type T4 = Unpacked<Promise<string>[]>;  // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>;  // string

内置泛型工具

TS 提供了一些罕用的类型工具。

Partial<T> (TypeScript 2.1)

将传入的类型的所有属性设置为可选。

type Partial<T> = {[P in keyof T]?: T[P]
}

Required<T> (TypeScript 2.8)

将传入的类型的所有属性设置为必选。

type Required<T> = {[P in keyof T]-?: T[P]
}

Readonly<T> (TypeScript 2.1)

将传入的类型的所有属性设置为只读。

type Readonly<T> = {readonly [P in keyof T]: T[P]
}

Record<K, T> (TypeScript 2.1)

将传入的 K 类型,从新定义为 T 类型。

type Record<K extends keyof any, T> = {[P in K]: T;
}

Exclude<T, K> (TypeScript 2.8)

从 T 类型中排除所有能够赋值给 U 的类型,生成新类型。

type Exclude<T, U> = T extends U ? never : T;

Extract<T, K> (TypeScript 2.8)

从 T 类型中提取所有能够赋值给 U 的类型,生成新类型。

type Extract<T, U> = T extends U ? T : never;

Pick<T, K> (TypeScript 2.1)

从 T 类型中提取 K 键名的元素,生成新类型。

type Pick<T, K extends keyof T> = {[P in K]: T[P];
}

Omit<T, K> (TypeScript 3.5)

从 T 类型中排除 K 键名的元素,生成新类型。

type Omit<T, K> = Pick<
  T, 
  Exclude<keyof T, K>
>

NonNullable<T> (TypeScript 2.8)

从 T 类型中排除 ull 或者 undefined 类型,生成新类型。

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

ReturnType<T> (TypeScript 2.8)

获取一个函数类型定义的返回类型。

type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

Parameters<T>

获取一个函数的参数类型。

type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;

ConstructorParameters<T>

获取一个构造函数的参数类型,以数组格局返回。

type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;

InstanceType<T> (TypeScript 2.8)

获取一个类的实例类型。

type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;

相干材料

TypeScript 文档
TyepScript Handbook
TypeScript Handbook 中文版
TypeScript Deep Dive
TypeScript Deep Dive 中文版
TypeScript 入门教程
TypeScript Web 版
TypeScript 内置工具泛型

正文完
 0