TypeScript类型标注相关概念一览

SegmentFault

共 10283字,需浏览 21分钟

 ·

2020-08-28 04:38

来源:SegmentFault 思否社区

作者:LnEoi




背景


用TypeScript重构了一遍业务后台,TS很大一部分在处理类型标注,基础的类型很容易上手,但到泛型一块,抽象程度一下子就高了起来,提供的许多工具也复杂了起来。重构的时候一直想整理一份方便查询的笔记一直没空,现在总算抽出时间整理了一份,将于类型有关的部分整理了出来,其他部分还需要自行翻阅相关文档。


笔记类似大纲,知道什么情况下有什么可以用,有一个范围概念,具体细节通过笔记提供的关键字再搜索相关文档仔细了解。





基础类型


TS是JS的超集,所以JS基础的类型都包含在内:
boolean、number、string、symbol、array([])、object({})、null、undefined


  • TS还提供了其他更加详细的类型标识:

  • any:任意类型,TS将不对其进行类型检测

  • unknown:未知类型,在早期对于未知类型我们一般标注any,但标注any将不会对类型进行检测,unknown则在对变量第一次赋值后,除了any类型,其他类型禁止再赋值给此变量,同时在使用其类型所对应的方法时也需要先收缩类型,才能正常的使用

  • void:无返回,当一个函数没有返回值的时候此类型标识

  • never:当函数永远没有返回值的时候用此类型标识。如始终抛出异常的函数,其返回值就是never

  • tuple:元组,数组的更精确的标识方法,可以限定数组的数量与每个位的类型

  • enum:枚举,可以给每一个预定值的数值或是字符串提供简明的名称,方便编写时区分

  • ... | ...:类型字面量,如'Red' | 'Green' | 'Blue',此时这变量就只能赋值这三种字符串,如果赋值其他字符串时将会报错,也可以是数字字面量 1 | 2 | 3


其他

  • 默认情况下null和undefined是所有类型的子类型,可以将这两个值赋值给任意类型。
  • never类型也是任何类型的子类型。但其他类型无法赋值给never类型,只有never才能赋值给never类型。
  • object与Object,这两个注释类型只有一个大小写之分,用Object申明的类型可以访问Object对象的默认方法,但object则不行。





基本概念


: (冒号标注)


TS有自动类型推断,当赋上初始值后TS会生成相应的类型标注,所以一般情况下不必手动添加


let variate: boolean = truelet variate: number = 1let variate: string = 'one'let variate: number[] = [1,3,4,6] // 数组let variate: { first: string, second: number } = { first: 'one', second: 2 } // 对象let sym1 = Symbol('key') // symbollet 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): numberfunction func(data: string): stringfunction 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}`;    }}

修饰符

修饰符与其他语言一样,public、private、protected。不标明时默认为public。
静态属性static,只读readonly,可选?。

存取器get、set

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、<>类型断言


<>断言写在前面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 | undefinedlet second = variate!.second


type 类型别名


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


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


甚至可以直接给原始类型起别名


type name = string


或是给interface再起别名


type funcParams = IParams


也可以使用&、|


type funcParams2 = IParams & IParams2type funcParams2 = IParams | IParams2





泛型


此前的基础概念大多数熟悉面向对象编程语言的可以很快就上手,但到泛型一块,概念性的东西是多了起来,语法也越渐变得复杂起来,TS制定了一套语法,除了写业务逻辑,我们还需要对JS类型进行编程。


为什么会有泛型呢?在一般使用中,类型相对固定,是可预期的。但如果要写通用组件时,我们没办法完全预期传入的类型和返回类型,有一些返回类型可能是需要用户自定义的,这时候就需要用到泛型来让用户在外部标注类型。


泛型符号


跟断言很像,但括号这里是在使用时传入的定义类型,命名为T,其中符号可以随意自定义,但有一些常用的关键字:T为type,U为T的下一位,K为key,V为Value,N为Number,E为Element


使用时类似这样:


function func(arg1: T, arg2: U): [T, U] {    return [arg1, arg2]}const func = (arg1: T, arg2: U) => [arg1, arg2]
class People { name: string; data: T}
// 接口中使用泛型interface IArg { 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构建我们处理工具Partialtype Partial = {  [P in keyof T]?: T[P]}// 当做泛型,将定义的类型传入type IParamsPartial = Partial // 是不是有类型编程的味道了
// 去除可选可用-号标识type Required = { [P in keyof T]-?: T[P]}type IParamsRequired = Required

extends继承

同样的逻辑,可以用来约束泛型的格式。用U extends TU继承自T,具有T中的定义,所以泛型传入的参数必须实现T中的定义

// 我们约束传入的值必须带name属性 并且值类型为stinginterface IArg {  name: string}function func(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(Object: T, key: K) {  return Object[key];}
const lsit = { a: 1, b: 2, c: 3 }getObjectValue(lsit, 'a'); // 通过 返回1getObjectValue(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 extends (infer U)[] ? U : // 如果传入的是数组 则将数组的类型命名为U 并且返回U类型    T extends (...args: any[]) => infer U ? U :  // 如果传入的是函数 则将函数的返回值命名为U 并且返回U类型    T extends Promise ? U :  // 如果传入的是Promise 则将Promise的泛型参数命名为U 并且返回U类型    T;  // 如果所有皆否 则返回T类型
type T0 = Unpacked; // stringtype T1 = Unpacked; // stringtype T2 = Unpacked<() => string>; // stringtype T3 = Unpacked>; // stringtype T4 = Unpacked[]>; // Promisetype T5 = Unpacked[]>>; // string


内置泛型工具


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

Partial (TypeScript 2.1)

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

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

Required (TypeScript 2.8)

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

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

Readonly (TypeScript 2.1)

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


type Readonly = {  readonly [P in keyof T]: T[P]}Record (TypeScript 2.1)

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


type Record = {  [P in K]: T;}Exclude (TypeScript 2.8)

从T类型中排除所有可以赋值给U的类型,生成新类型

type Exclude = T extends U ? never : T;
Extract (TypeScript 2.8)

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


type Extract = T extends U ? T : never;
Pick (TypeScript 2.1)

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

type Pick = {  [P in K]: T[P];}

Omit (TypeScript 3.5)

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

type Omit = Pick<  T,  Exclude>

NonNullable (TypeScript 2.8)

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


type NonNullable = T extends null | undefined ? never : T;ReturnType (TypeScript 2.8)

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


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

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

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

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


type ConstructorParameters any> = T extends new (...args: infer P) => any ? P : never;
InstanceType (TypeScript 2.8)


获取一个类的实例类型。


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


相关资料


TypeScript文档:https://www.tslang.cn/docs/home.html

TyepScript Handbook:

https://www.typescriptlang.org/docs/handbook/intro.html
TypeScript Handbook 中文版:

https://zhongsp.gitbooks.io/typescript-handbook/content/

TypeScript Deep Dive:https://basarat.gitbook.io/typescript/
TypeScript Deep Dive 中文版:https://jkchao.github.io/typescript-book-chinese/
TypeScript入门教程:https://ts.xcatliu.com/
TypeScript Web版:https://www.typescriptlang.org/zh/play
TypeScript 内置工具泛型:

https://github.com/chenxiaochun/blog/issues/67





点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流。

- END -


浏览 52
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报