竹笋

首页 » 问答 » 环境 » TypeScript掌握TypeSc
TUhjnbcbe - 2023/2/28 18:40:00

自我介绍:大家好,我是吉帅振的网络日志(其他平台账号名字相同),互联网前端开发工程师,工作5年,去过上海和北京,经历创业公司,加入过阿里本地生活团队,现在郑州北游教育从事编程培训。

###一、前言

在TypeScript中提供了许多自带的工具类型,因为这些类型都是全局可用的,所以无须导入即可直接使用。了解了基础的工具类型后,我们不仅知道TypeScript如何利用前几讲介绍的基础类型知识实现这些工具类型,还知道如何更好地利用这些基础类型,以免重复造轮子,并能通过这些工具类型实现更复杂的类型。

###二、操作接口类型

Partial工具类型可以将一个类型的所有属性变为可选的,且该工具类型返回的类型是给定类型的所有子集,下面我们看一个具体的示例:

```

typePartialT={

[PinkeyofT]?:T[P];

};

interfacePerson{

name:string;

age?:number;

weight?:number;

}

typePartialPerson=PartialPerson;

//相当于

interfacePartialPerson{

name?:string;

age?:number;

weight?:number;

}

```

在上述示例中,我们使用映射类型取出了传入类型的所有键值,并将其值设定为可选的。与Partial工具类型相反,Required工具类型可以将给定类型的所有属性变为必填的,下面我们看一个具体示例。

```

typeRequiredT={

[PinkeyofT]-?:T[P];

};

typeRequiredPerson=RequiredPerson;

//相当于

interfaceRequiredPerson{

name:string;

age:number;

weight:number;

}

```

在上述示例中,映射类型在键值的后面使用了一个-符号,-与?组合起来表示去除类型的可选属性,因此给定类型的所有属性都变为了必填。Readonly工具类型可以将给定类型的所有属性设为只读,这意味着给定类型的属性不可以被重新赋值,下面我们看一个具体的示例。

```

typeReadonlyT={

readonly[PinkeyofT]:T[P];

};

typeReadonlyPerson=ReadonlyPerson;

//相当于

interfaceReadonlyPerson{

readonlyname:string;

readonlyage?:number;

readonlyweight?:number;

}

```

在上述示例中,经过Readonly处理后,ReadonlyPerson的name、age、weight等属性都变成了readonly只读。Pick工具类型可以从给定的类型中选取出指定的键值,然后组成一个新的类型,下面我们看一个具体的示例。

```

typePickT,KextendskeyofT={

[PinK]:T[P];

};

typeNewPerson=PickPerson,name

age;

//相当于

interfaceNewPerson{

name:string;

age?:number;

}

```

在上述示例中,Pick工具类型接收了两个泛型参数,第一个T为给定的参数类型,而第二个参数为需要提取的键值key。有了参数类型和需要提取的键值key,我们就可以通过映射类型很容易地实现Pick工具类型的功能。与Pick类型相反,Omit工具类型的功能是返回去除指定的键值之后返回的新类型,下面我们看一个具体的示例:

```

typeOmitT,Kextendskeyofany=PickT,ExcludekeyofT,K;

typeNewPerson=OmitPerson,weight;

//相当于

interfaceNewPerson{

name:string;

age?:number;

}

```

在上述示例中,Omit类型的实现使用了前面介绍的Pick类型。我们知道Pick类型的作用是选取给定类型的指定属性,那么这里的Omit的作用应该是选取除了指定属性之外的属性,而Exclude工具类型的作用就是从入参T属性的联合类型中排除入参K指定的若干属性。操作接口类型这一小节所介绍的工具类型都使用了映射类型。通过映射类型,我们可以对原类型的属性进行重新映射,从而组成想要的类型。

###三、联合类型

在介绍Omit类型的实现中,我们使用了Exclude类型。通过使用Exclude类型,我们从接口的所有属性中去除了指定属性,因此,Exclude的作用就是从联合类型中去除指定的类型。

```

typeExcludeT,U=TextendsU?never:T;

typeT=Excludea

b

c,a;//=b

c

typeNewPerson=OmitPerson,weight;

//相当于

typeNewPerson=PickPerson,ExcludekeyofPerson,weight;

//其中

typeExcludeKeys=ExcludekeyofPerson,weight;//=name

age

```

在上述示例中,Exclude的实现使用了条件类型。如果类型T可被分配给类型U,则不返回类型T,否则返回此类型T,这样我们就从联合类型中去除了指定的类型。再回看之前的NewPerson类型的例子,我们也就很明白了。在ExcludeKeys中,如果Person类型的属性是我们要去除的属性,则不返回该属性,否则返回其类型。Extract类型的作用与Exclude正好相反,Extract主要用来从联合类型中提取指定的类型,类似于操作接口类型中的Pick类型。

```

typeExtractT,U=TextendsU?T:never;

typeT=Extracta

b

c,a;//=a

```

通过上述示例,我们发现Extract类型相当于取出两个联合类型的交集。此外,我们还可以基于Extract实现一个获取接口类型交集的工具类型,如下示例:

```

typeIntersectT,U={

[KinExtractkeyofT,keyofU]:T[K];

};

interfacePerson{

name:string;

age?:number;

weight?:number;

}

interfaceNewPerson{

name:string;

age?:number;

}

typeT=IntersectPerson,NewPerson;

//相当于

typeT={

name:string;

age?:number;

};

```

在上述的例子中,我们使用了Extract类型来提取两个接口类型属性的交集,并使用映射类型生成了一个新的类型。NonNullable的作用是从联合类型中去除null或者undefined的类型。如果你对条件类型已经很熟悉了,那么应该知道如何实现NonNullable类型了。

```

typeNonNullableT=Textendsnull

undefined?never:T;

//等同于使用Exclude

typeNonNullableT=ExcludeT,null

undefined;

typeT=NonNullablestring

number

undefined

null;//=string

number

```

在上述示例中,如果NonNullable传入的类型可以被分配给null或是undefined,则不返回该类型,否则返回其具体类型。Record的作用是生成接口类型,然后我们使用传入的泛型参数分别作为接口类型的属性和值。

```

typeRecordKextendskeyofany,T={

[PinK]:T;

};

typeMenuKey=home

about

more;

interfaceMenu{

label:string;

hidden?:boolean;

}

constmenus:RecordMenuKey,Menu={

about:{label:关于},

home:{label:主页},

more:{label:更多,hidden:true},

};

```

在上述示例中,Record类型接收了两个泛型参数:第一个参数作为接口类型的属性,第二个参数作为接口类型的属性值。需要注意:这里的实现限定了第一个泛型参数继承自keyofany。在TypeScript中,keyofany指代可以作为对象键的属性,如下示例:

```

typeT=keyofany;//=string

number

symbol

说明:目前,JavaScript仅支持string、number、symbol作为对象的键值。

```

###四、函数类型

ConstructorParameters可以用来获取构造函数的构造参数,而ConstructorParameters类型的实现则需要使用infer关键字推断构造参数的类型。关于infer关键字,我们可以把它当成简单的模式匹配来看待。如果真实的参数类型和infer匹配的一致,那么就返回匹配到的这个类型。

```

typeConstructorParametersTextendsnew(...args:any)=any=Textendsnew(

...args:inferP

)=any

?P

:never;

classPerson{

constructor(name:string,age?:number){}

}

typeT=ConstructorParameterstypeofPerson;//[name:string,age?:number]

```

在上述示例中,ConstructorParameters泛型接收了一个参数,并且限制了这个参数需要实现构造函数。于是,我们通过infer关键字匹配了构造函数内的构造参数,并返回了这些参数。因此,可以看到第11行匹配了Person构造函数的两个参数,并返回了一个元组类型[string,number]给类型别名T。Parameters的作用与ConstructorParameters类似,Parameters可以用来获取函数的参数并返回序对,如下示例:

```

typeParametersTextends(...args:any)=any=Textends(...args:inferP)=any?P:never;

typeT0=Parameters()=void;//[]

typeT1=Parameters(x:number,y?:string)=void;//[x:number,y?:string]

```

在上述示例中,Parameters的泛型参数限制了传入的类型需要满足函数类型。ReturnType的作用是用来获取函数的返回类型,下面我们看一个具体的示例:

```

typeReturnTypeTextends(...args:any)=any=Textends(...args:any)=inferR?R:any;

typeT0=ReturnType()=void;//=void

typeT1=ReturnType()=string;//=string

```

在上述示例中,ReturnType的泛型参数限制了传入的类型需要满足函数类型。ThisParameterType可以用来获取函数的this参数类型。关于函数的this参数,我们在05讲函数类型中介绍过,下面看一个具体的示例:

```

typeThisParameterTypeT=Textends(this:inferU,...args:any[])=any?U:unknown;

typeT=ThisParameterType(this:Number,x:number)=void;//Number

```

在上述示例的第1行中,因为函数类型的第一个参数声明的是this参数类型,所以我们可以直接使用infer关键字进行匹配并获取this参数类型。在示例的第3行,类型别名T得到的类型就是Number。ThisType的作用是可以在对象字面量中指定this的类型。ThisType不返回转换后的类型,而是通过ThisType的泛型参数指定this的类型,下面看一个具体的示例:如果你想使用这个工具类型,那么需要开启noImplicitThis的TypeScript配置。

```

typeObjectDescriptorD,M={

data?:D;

methods?:MThisTypeDM;//methods中this的类型是DM

};

functionmakeObjectD,M(desc:ObjectDescriptorD,M):DM{

letdata:object=desc.data

{};

letmethods:object=desc.methods

{};

return{...data,...methods}asDM;

}

constobj=makeObject({

data:{x:0,y:0},

methods:{

moveBy(dx:number,dy:number){

this.x+=dx;//this=DM

this.y+=dy;//this=DM

},

},

});

obj.x=10;

obj.y=20;

obj.moveBy(5,5);

```

在上述示例子中,methods属性的this类型为DM,在上下文中指代{x:number,y:number}{moveBy(dx:number,dy:number):void}。ThisType工具类型只是提供了一个空的泛型接口,仅可以在对象字面量上下文中被TypeScript识别,如下所示:

```

interfaceThisTypeT{}

也就是说该类型的作用相当于任意空接口。

OmitThisParameter

```

OmitThisParameter工具类型主要用来去除函数类型中的this类型。如果传入的函数类型没有显式声明this类型,那么返回的仍是原来的函数类型。

```

typeOmitThisParameterT=unknownextendsThisParameterTypeT

?T

:Textends(...args:inferA)=inferR

?(...args:A)=R

:T;

typeT=OmitThisParameter(this:Number,x:number)=string;//(x:number)=string

```

在上述示例中,ThisParameterType类型的实现如果传入的泛型参数无法推断this的类型,则会返回unknown类型。在OmitThisParameter的实现中,第一个条件语句如果传入的函数参数没有this类型,则返回原类型;否则通过infer分别获取函数参数和返回值的类型构造一个新的没有this的函数类型,并返回这个函数类型。

###五、字符串类型

**模板字符串**

TypeScript自4.1版本起开始支持模板字符串字面量类型。为此,TypeScript也提供了Uppercase、Lowercase、Capitalize、Uncapitalize这4种内置的操作字符串的类型,如下示例:

```

//转换字符串字面量到大写字母

typeUppercaseSextendsstring=intrinsic;

//转换字符串字面量到小写字母

typeLowercaseSextendsstring=intrinsic;

//转换字符串字面量的第一个字母为大写字母

typeCapitalizeSextendsstring=intrinsic;

//转换字符串字面量的第一个字母为小写字母

typeUncapitalizeSextendsstring=intrinsic;

typeT0=UppercaseHello;//=HELLO

typeT1=LowercaseT0;//=hello

typeT2=CapitalizeT1;//=Hello

typeT3=UncapitalizeT2;//=hello

```

在上述示例中,这4种操作字符串字面量工具类型的实现都是使用JavaScript运行时的字符串操作函数计算出来的,且不支持语言区域设置。以下代码是这4种字符串工具类型的实际实现。

```

functionapplyStringMapping(symbol:Symbol,str:string){

switch(intrinsicTypeKinds.get(symbol.escapedNameasstring)){

caseIntrinsicTypeKind.Uppercase:

returnstr.toUpperCase();

caseIntrinsicTypeKind.Lowercase:

returnstr.toLowerCase();

caseIntrinsicTypeKind.Capitalize:

returnstr.charAt(0).toUpperCase()+str.slice(1);

caseIntrinsicTypeKind.Uncapitalize:

returnstr.charAt(0).toLowerCase()+str.slice(1);

}

returnstr;

}

```

在上述代码中可以看到,字符串的转换使用了JavaScript中字符串的toUpperCase和toLowerCase方法,而不是toLocaleUpperCase和toLocaleLowerCase。其中toUpperCase和toLowerCase采用的是Unicode编码默认的大小写转换规则。

###六、总结

学习操作接口类型、联合类型、函数、字符串的工具类。掌握类型操作的技巧,自由地组合更多的工具类型。

1
查看完整版本: TypeScript掌握TypeSc