TypeScript 类型操作(typescript菜鸟教程)
前言
在掌握好 TypeScript
基础之后,就需要将基础类型联合起来做出更优美的类型声明。
TypeScript
中有很多关键字,如果不去主动接触的话就会少了很多可操作性。例如:infer
、keyof
、typeof
、extends
之类的。还有映射和模板字符串这种概念
泛型
利用泛型来达到类型复用的目的,而不是一昧的用 any
大法。这里达到的效果是传入什么类型,就返回该类型
function identify<T>(arg: T): T { return arg; } 复制代码
接口的形式定义泛型
interface GenericIdentifyFn { <T>(arg: T): T } function identify<T>(arg: T): T { return arg } let myIdentify: GenericIdentifyFn = identify 复制代码
在类型的继承中使用
class BeeKeeper { hasMask!: boolean; } class ZooKeeper { nametag!: string; } class Animal { numLegs!: number; } class Bee extends Animal { keeper: BeeKeeper = new BeeKeeper; } class Lion extends Animal { keeper: ZooKeeper = new ZooKeeper; } function createInstance<A extends Animal>(c: new () => A): A { return new c(); } createInstance(Lion).keeper.nametag; // typechecks! createInstance(Bee).keeper.hasMask; // typechecks! 复制代码
keyof
运算符:构造一个类型,类型由参数的所有键值
interface Person { name: string; age: number; } type T1 = keyof Person; // "name" | "age" type T2 = keyof { [x: string]: 1 }; // string | number type T3 = number; type T4 = keyof T3; // type T4 = "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString" function prop<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key] } 复制代码
提取键值,组成类型。提取一定要是的类型
typeof
运算符:获取变量的类型
type Predicate = (x: unknown) => boolean; function f() { return true; } type K = ReturnType<Predicate>; // type K = boolean type T = ReturnType<typeof f>; // type T = boolean 复制代码
JavaScript
中也有 typeof
。TypeScript
环境下,编辑器能够区分应该使用哪个
keyof
和 typeof
这两个都是提取类型的,一个是提取类型,一个是提取变量。可以一起记忆
索引
对于对象,通过属性名索引
type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"]; // type Age = number 复制代码
对于数组,通过数值索引
const MyArray = [ { name: "Alice", age1: 15 }, { name: "Bob", age: 23 }, { name: "Eve", age: 38 }, ]; type Person = NonNullable<typeof MyArray[number]["age1"]>; // type Person = number 复制代码
索引的方式可以抽离我们想要的类型
条件类型
条件类型有助于描述输入和输出类型之间的关系,操作起来更加灵活
interface Animal { live(): void; } interface Dog extends Animal { woof(): void; } type Example1 = Dog extends Animal ? number : string; type Example2 = RegExp extends Animal ? number : string; 复制代码
上面这个是 extends 的条件用法,类似三元表达式语法
重载函数的例子
interface IdLabel { id: number; } interface NameLabel { name: string; } function createLabel(id: number): IdLabel; function createLabel(name: string): NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel { throw "unimplemented"; } 复制代码
这里进行了三个重载。我们可以改造成下述的形式,将输出定义成一个条件类型,可以避免写复杂的重载函数
type nameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel; function createLabel<T extends number | number>(idOrName: T): NameOrId { thorw "unimplemented"; } 复制代码
条件类型约束
更好的限制泛型
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never; 复制代码
type Flatten<T> = T extends any[] ? T[number] : T; 复制代码
在条件类型中进行推断 infer
注意:只有在条件类型 extends 子句中才允许 infer 声明
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type; 复制代码
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never type Num = GetReturnType<() => number>; type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>; 复制代码
主要体现了 infer 的用法,作为辅助类型的方式出现
分配条件类型
当我们向条件类型传入联合类型,如果没有使用 []
会出现映射联合类型的每个成员类型。
type ToArray<T> = T extends any ? T[] : never; type ToArray2<T> = [T] extends [any] ? T[] : never; type StrArrOrNumArr = ToArray<string | number>; // string[] | number[] type StrArrOrNumArr2 = ToArray2<string | number>; // (string | number)[] 复制代码
映射类型
当我们想要将另一个类型的属性都拿过来的时候,可以用到映射的概念。keyof
拿出所有类型,in
表示在这些类型中
type Horse = {}; type OnlyBoolsAndHorses = { [key: string]: boolean | Horse; }; const conforms: OnlyBoolsAndHorses = { del: true, rodney: false, }; type OptionsFlags<Type> = { [Property in keyof Type]: boolean; }; 复制代码
映射修饰符
readonly
和 ?
可以在映射的过程中使用。用 -
+
表示减少或添加,默认 +
type CreateMutable<Type> = { -readonly [Property in keyof Type]?: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>; // type UnlockedAccount = { // id?: string | undefined; // name?: string | undefined; // } 复制代码
通过 as 来重新设置键名
as
可以将键名设置成我们想要的形式,或者通过 never
过滤
联合模式
type EventConfig<Events extends { kind: string }> = { [E in Events as E["kind"]]: (event: E) => void; }; type SquareEvent = { kind: "square"; x: number; y: number }; type CircleEvent = { kind: "circle"; radius: number }; type Config = EventConfig<SquareEvent | CircleEvent>; // type Config = { // square: (event: SquareEvent) => void; // circle: (event: CircleEvent) => void; // } 复制代码
这个很好用,也很好玩
模板字符串
类似 js
中的模板字符串语法。遇到联合类型会算出每种组合方式
type World = "world"; type Greeting = `hello ${World}`; // type Greeting = "hello world" type EmailLocaleIDs = "welcome_email" | "email_heading"; type FooterLocaleIDs = "footer_title" | "footer_sendoff"; type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; // type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id" type Lang = "en" | "ja" | "pt"; type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`; // type LocaleMessageIDs = "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id" 复制代码
类型中的字符串联合
意思是将类型的属性拿出来进行字符串拼接,可以得到一个联合类型。
type PropEventSource<Type> = { on( eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void ): void; }; declare function makeWatchedObject<Type>( obj: Type ): Type & PropEventSource<Type>; const person = makeWatchedObject({ firstName: "Saoirse", lastName: "Ronan", age: 26, }); person.on("firstNameChanged", (newValue: any) => { console.log(`firstName was changed to ${newValue}!`); }); 复制代码
额,好像没啥不好理解的
使用模板文字进行推理
用模板字符串中的变量来做泛型的类型推断
内在字符串操作类型
intrinsic
是一个关键字,是一种编译
intrinsic
关键字只能用于声明编译器提供的内部类型。
Uppercase<StringType>
将字符串中的每个字符转换为大写版本
type Greeting = "Hello, world"; type ShoutyGreeting = Uppercase<Greeting>; // type ShoutyGreeting = "HELLO, WORLD" type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`; type MainID = ASCIICacheKey<"my_app">; // type MainID = "ID-MY_APP" 复制代码
源码
type Uppercase<S extends string> = intrinsic; 复制代码
Lowercase<StringType>
将字符串中的每个字符转换为小写版本
type Greeting = "Hello, world"; type QuietGreeting = Lowercase<Greeting>; // type QuietGreeting = "hello, world" type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`; type MainID = ASCIICacheKey<"MY_APP">; // type MainID = "id-my_app" 复制代码
源码
type Lowercase<S extends string> = intrinsic; 复制代码
Capitalize<StringType>
将字符串中的第一个字符转换为等效的大写字符
type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>; // type Greeting = "Hello, world" 复制代码
源码
type Capitalize<S extends string> = intrinsic; 复制代码
Uncapitalize<StringType>
将字符串中的第一个字符转换为等效的小写字符
type UppercaseGreeting = "HELLO WORLD"; type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>; // type UncomfortableGreeting = "hELLO WORLD" 复制代码
源码
type Uncapitalize<S extends string> = intrinsic; 复制代码
小结
类型操作让我们更加灵活的使用类型,优化很多繁琐的声明。对于每个知识点都需要熟记,毕竟存在即合理,肯定有它的妙用所在
伪原创工具 SEO网站优化 https://www.237it.com/
作者:lankongclub
链接:https://juejin.cn/post/7035995700630388743