TypeScript 基础笔记 ——泛型约束|泛型类(TS -- 14下)

简介: 泛型约束|泛型类(TS -- 14下)

使用 keyof 约束对象

其中使用了 TS 泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字继承 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,它的返回 类型是联合 类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型

functionprop<T, KextendskeyofT>(obj: T, key: K) {

  returnobj[key]

}

 

leto= { a: 1, b: 2, c: 3 }

 

prop(o, 'a')

prop(o, 'd') //,我们需要约束一下这个o里面并没有的东西,此时就会报错发现找不到

//通过提示,我门可以看到类型"d"的参数不能赋给类型"a"|"b"|"c"的参数

泛型类

声明方法跟函数类似名称后面定义 <类型>

使用的时候确定类型 new Sub<number>()

//定义泛型的一个类

classSub<T>{

   attr:T[] = []//这里的:只是普通的:

   add(a:T):T[]{

       return [a]

   }

}

lets=newSub<number>()//这里已经使用泛型固定为number了

s.attr= [123]//正常运行

s.attr= ['123']//报错

s.add(123)//也是只能传数字

letstr=newSub<string>()//这里已经使用泛型固定为number了

str.attr= [123]//报错

str.attr= ['123']//正常运行

str.add('123')//也是只能传字符串

console.log(s,str)

泛型工具类型(大量补充额外内容)

作者使用了Typora作为写笔记的编辑器,这里可以对目录进行折叠方面我们查阅我们想要的部分

网络异常,图片无法展示
|

为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者可以更好的学习其它的工具类型。

1.typeof

typeof 的主要用途是在类型上下文中获取变量或者属性的类型,下面我们通过一个具体示例来理解一下。

interfacePerson {

 name: string;

 age: number;

}

constsem: Person= { name: "semlinker", age: 30 };

typeSem=typeofsem; // type Sem = Person

在上面代码中,我们通过 typeof 操作符获取 sem 变量的类型并赋值给 Sem 类型变量,之后我们就可以使用 Sem 类型:

constlolo: Sem= { name: "lolo", age: 5 }

你也可以对嵌套对象执行相同的操作:

constMessage= {

   name: "jimmy",

   age: 18,

   address: {

     province: '四川',

     city: '成都'  

   }

}

typemessage=typeofMessage;

/*

type message = {

   name: string;

   age: number;

   address: {

       province: string;

       city: string;

   };

}

*/

此外,typeof 操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型,比如:

functiontoArray(x: number): Array<number> {

 return [x];

}

typeFunc=typeoftoArray; // -> (x: number) => number[]

2.keyof

keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

interfacePerson {

 name: string;

 age: number;

}

typeK1=keyofPerson; // "name" | "age"

typeK2=keyofPerson[]; // "length" | "toString" | "pop" | "push" | "concat" | "join"

typeK3=keyof { [x: string]: Person };  // string | number

在 TypeScript 中支持两种索引签名,数字索引和字符串索引:

interfaceStringArray {

 // 字符串索引 -> keyof StringArray => string | number

 [index: string]: string;

}

interfaceStringArray1 {

 // 数字索引 -> keyof StringArray1 => number

 [index: number]: string;

}

为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引。所以 keyof { [x: string]: Person } 的结果会返回 string | number

keyof 也支持基本数据类型:

let K1: keyof boolean; // let K1: "valueOf"

let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...

let K3: keyof symbol; // let K1: "valueOf"

keyof 的作用

JavaScript 是一种高度动态的语言。有时在静态类型系统中捕获某些操作的语义可能会很棘手。以一个简单的 prop 函数为例:

functionprop(obj, key) {

 returnobj[key];

}

该函数接收 obj 和 key 两个参数,并返回对应属性的值。对象上的不同属性,可以具有完全不同的类型,我们甚至不知道 obj 对象长什么样。

那么在 TypeScript 中如何定义上面的 prop 函数呢?我们来尝试一下:

functionprop(obj: object, key: string) {

 returnobj[key];

}

在上面代码中,为了避免调用 prop 函数时传入错误的参数类型,我们为 obj 和 key 参数设置了类型,分别为 {}string 类型。然而,事情并没有那么简单。针对上述的代码,TypeScript 编译器会输出以下错误信息:

Elementimplicitlyhasan'any'typebecauseexpressionoftype'string'can't be used to index type '{}'.

元素隐式地拥有 any 类型,因为 string 类型不能被用于索引 {} 类型。要解决这个问题,你可以使用以下非常暴力的方案:

functionprop(obj: object, key: string) {

 return (objasany)[key];

}

很明显该方案并不是一个好的方案,我们来回顾一下 prop 函数的作用,该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用本文的主角 keyof 操作符:

functionprop<Textendsobject, KextendskeyofT>(obj: T, key: K) {

 returnobj[key];

}

在以上代码中,我们使用了 TypeScript 的泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字约束该类型必须是 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型。 是骡子是马拉出来遛遛就知道了,我们来实际测试一下:

typeTodo= {

 id: number;

 text: string;

 done: boolean;

}

consttodo: Todo= {

 id: 1,

 text: "Learn TypeScript keyof",

 done: false

}

functionprop<Textendsobject, KextendskeyofT>(obj: T, key: K) {

 returnobj[key];

}

constid=prop(todo, "id"); // const id: number

consttext=prop(todo, "text"); // const text: string

constdone=prop(todo, "done"); // const done: boolean

很明显使用泛型,重新定义后的 prop<T extends object, K extends keyof T>(obj: T, key: K) 函数,已经可以正确地推导出指定键对应的类型。那么当访问 todo 对象上不存在的属性时,会出现什么情况?比如:

constdate=prop(todo, "date");

对于上述代码,TypeScript 编译器会提示以下错误:

Argumentoftype'"date"'isnotassignabletoparameteroftype'"id" | "text" | "done"'.

这就阻止我们尝试读取不存在的属性。

3.in

in 用来遍历枚举类型:

typeKeys="a"|"b"|"c"

typeObj=  {

 [pinKeys]: any

} // -> { a: any, b: any, c: any }

4.infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

typeReturnType<T>=Textends (

 ...args: any[]

) =>inferR?R : any;

以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。

5.extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interfaceLengthwise {

 length: number;

}

functionloggingIdentity<TextendsLengthwise>(arg: T): T {

 console.log(arg.length);

 returnarg;

}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

这时我们需要传入符合约束类型的值,必须包含 length 属性:

loggingIdentity({length: 10, value: 3});

索引类型

在实际开发中,我们经常能遇到这样的场景,在对象中获取一些属性的值,然后建立对应的集合。

letperson= {

   name: 'musion',

   age: 35

}

functiongetValues(person: any, keys: string[]) {

   returnkeys.map(key=>person[key])

}

console.log(getValues(person, ['name', 'age'])) // ['musion', 35]

console.log(getValues(person, ['gender'])) // [undefined]

在上述例子中,可以看到 getValues (persion, ['gender']) 打印出来的是 [undefined],但是 ts 编译器并没有给出报错信息,那么如何使用 ts 对这种模式进行类型约束呢?这里就要用到了索引类型,改造一下 getValues 函数,通过 索引类型查询索引访问 操作符:

functiongetValues<T, KextendskeyofT>(person: T, keys: K[]): T[K][] {

 returnkeys.map(key=>person[key]);

}

interfacePerson {

   name: string;

   age: number;

}

constperson: Person= {

   name: 'musion',

   age: 35

}

getValues(person, ['name']) // ['musion']

getValues(person, ['gender']) // 报错:

// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.

// Type "gender" is not assignable to type "name" | "age".

编译器会检查传入的值是否是 Person 的一部分。通过下面的概念来理解上面的代码:

T[K]表示对象T的属性K所表示的类型,在上述例子中,T[K][] 表示变量T取属性K的值的数组

// 通过[]索引类型访问操作符, 我们就能得到某个索引的类型

classPerson {

   name:string;

   age:number;

}

typeMyType=Person['name'];  //Person中name的类型为string type MyType = string

介绍完概念之后,应该就可以理解上面的代码了。首先看泛型,这里有 T 和 K 两种类型,根据类型推断,第一个参数 person 就是 person,类型会被推断为 Person。而第二个数组参数的类型推断(K extends keyof T),keyof 关键字可以获取 T,也就是 Person 的所有属性名,即 ['name', 'age']。而 extends 关键字让泛型 K 继承了 Person 的所有属性名,即 ['name', 'age']。这三个特性组合保证了代码的动态性和准确性,也让代码提示变得更加丰富了

getValues(person, ['gender']) // 报错:

// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.

// Type "gender" is not assignable to type "name" | "age".

映射类型

根据旧的类型创建出新的类型,我们称之为映射类型

比如我们定义一个接口

interfaceTestInterface{

   name:string,

   age:number

}

我们把上面定义的接口里面的属性全部变成可选

// 我们可以通过+/-来指定添加还是删除

typeOptionalTestInterface<T>= {

 [pinkeyofT]+?:T[p]

}

typenewTestInterface=OptionalTestInterface<TestInterface>

// type newTestInterface = {

//    name?:string,

//    age?:number

// }

比如我们再加上只读

typeOptionalTestInterface<T>= {

+readonly [pinkeyofT]+?:T[p]

}

typenewTestInterface=OptionalTestInterface<TestInterface>

// type newTestInterface = {

//   readonly name?:string,

//   readonly age?:number

// }

由于生成只读属性和可选属性比较常用,所以 TS 内部已经给我们提供了现成的实现 Readonly / Partial, 会面内置的工具类型会介绍.

内置的工具类型

Partial

Partial<T> 将类型的属性变成可选

定义

typePartial<T>= {

 [PinkeyofT]?: T[P];

};

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值的类。中间的 ? 号,用于将所有属性变为可选。

举例说明

interfaceUserInfo {

   id: string;

   name: string;

}

// error:Property 'id' is missing in type '{ name: string; }' but required in type 'UserInfo'

constxiaoming: UserInfo= {

   name: 'xiaoming'

}

使用  Partial<T>

typeNewUserInfo=Partial<UserInfo>;

constxiaoming: NewUserInfo= {

   name: 'xiaoming'

}

这个  NewUserInfo 就相当于

interfaceNewUserInfo {

   id?: string;

   name?: string;

}

但是 Partial<T> 有个局限性,就是只支持处理第一层的属性,如果我的接口定义是这样的

interfaceUserInfo {

   id: string;

   name: string;

   fruits: {

       appleNumber: number;

       orangeNumber: number;

   }

}

typeNewUserInfo=Partial<UserInfo>;

// Property 'appleNumber' is missing in type '{ orangeNumber: number; }' but required in type '{ appleNumber: number; orangeNumber: number; }'.

constxiaoming: NewUserInfo= {

   name: 'xiaoming',

   fruits: {

       orangeNumber: 1,

   }

}

可以看到,第二层以后就不会处理了,如果要处理多层,就可以自己实现

DeepPartial

typeDeepPartial<T>= {

    // 如果是 object,则递归类型

   [UinkeyofT]?: T[U] extendsobject

     ?DeepPartial<T[U]>

     : T[U]

};

typePartialedWindow=DeepPartial<T>; // 现在T上所有属性都变成了可选啦

Required

Required 将类型的属性变成必选

定义

typeRequired<T>= {

   [PinkeyofT]-?: T[P]

};

其中 -? 是代表移除 ? 这个 modifier 的标识。再拓展一下,除了可以应用于 ? 这个 modifiers ,还有应用在 readonly ,比如 Readonly<T> 这个类型

typeReadonly<T>= {

   readonly [pinkeyofT]: T[p];

}

Readonly

Readonly<T> 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。

定义

typeReadonly<T>= {

readonly [PinkeyofT]: T[P];

};

举例说明

interfaceTodo {

title: string;

}

consttodo: Readonly<Todo>= {

title: "Delete inactive users"

};

todo.title="Hello"; // Error: cannot reassign a readonly property

Pick

Pick 从某个类型中挑出一些属性出来

定义

typePick<T, KextendskeyofT>= {

   [PinK]: T[P];

};

举例说明

interfaceTodo {

 title: string;

 description: string;

 completed: boolean;

}

typeTodoPreview=Pick<Todo, "title"|"completed">;

consttodo: TodoPreview= {

 title: "Clean room",

 completed: false,

};

可以看到 NewUserInfo 中就只有个 name 的属性了。

Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

定义

typeRecord<Kextendskeyofany, T>= {

   [PinK]: T;

};

举例说明

interfacePageInfo {

 title: string;

}

typePage="home"|"about"|"contact";

constx: Record<Page, PageInfo>= {

 about: { title: "about" },

 contact: { title: "contact" },

 home: { title: "home" },

};

ReturnType

用来得到一个函数的返回值类型

定义

typeReturnType<Textends (...args: any[]) =>any>=Textends (

 ...args: any[]

) =>inferR

 ?R

 : any;

infer 在这里用于提取函数类型的返回值类型。ReturnType<T> 只是将 infer R 从参数位置移动到返回值位置,因此此时 R 即是表示待推断的返回值类型。

举例说明

typeFunc= (value: number) =>string;

constfoo: ReturnType<Func>="1";

ReturnType 获取到 Func 的返回值类型为 string,所以,foo 也就只能被赋值为字符串了。

Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。

定义

typeExclude<T, U>=TextendsU?never : T;

如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉。

举例说明

typeT0=Exclude<"a"|"b"|"c", "a">; // "b" | "c"

typeT1=Exclude<"a"|"b"|"c", "a"|"b">; // "c"

typeT2=Exclude<string|number| (() =>void), Function>; // string | number

Extract

Extract<T, U> 的作用是从 T 中提取出 U

定义

typeExtract<T, U>=TextendsU?T : never;

举例说明

typeT0=Extract<"a"|"b"|"c", "a"|"f">; // "a"

typeT1=Extract<string|number| (() =>void), Function>; // () =>void

Omit

Omit<T, K extends keyof any> 的作用是使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型。

定义

typeOmit<T, Kextendskeyofany>=Pick<T, Exclude<keyofT, K>>;

举例说明

interfaceTodo {

 title: string;

 description: string;

 completed: boolean;

}

typeTodoPreview=Omit<Todo, "description">;

consttodo: TodoPreview= {

 title: "Clean room",

 completed: false,

};

NonNullable

NonNullable<T> 的作用是用来过滤类型中的 nullundefined 类型。

定义

typeNonNullable<T>=Textendsnull|undefined?never : T;

举例说明

typeT0=NonNullable<string|number|undefined>; // string | number

typeT1=NonNullable<string[] |null|undefined>; // string[]

Parameters

Parameters<T> 的作用是用于获得函数的参数类型组成的元组类型。

定义

typeParameters<Textends (...args: any) =>any>=Textends (...args: inferP) =>any

?P : never;

举例说明

typeA=Parameters<() =>void>; // []

typeB=Parameters<typeofArray.isArray>; // [any]

typeC=Parameters<typeofparseInt>; // [string, (number | undefined)?]

typeD=Parameters<typeofMath.max>; // number[]

目录
相关文章
|
2月前
|
JavaScript
typeScript基础(3)_ts函数默认值和可选参数
本文介绍了在TypeScript中如何使用函数的默认值和可选参数。展示了如何为函数参数指定默认值,使得在调用函数时可以省略某些参数,以及如何定义可选参数。
176 2
|
1月前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
1月前
|
JavaScript 前端开发
Vue2整合TypeScript:借助vue-property-decorator以类模式编写组件
Vue2整合TypeScript:借助vue-property-decorator以类模式编写组件
140 3
|
2月前
|
JavaScript 安全
typeScript进阶(14)_泛型和注意事项
TypeScript中的泛型允许创建可重用的代码。泛型可以定义函数、接口、类,支持传递类型参数,实现类型安全。泛型可以用于数组,约束类型参数必须符合特定的接口,也可以在接口和类中使用。泛型类可以包含多个类型参数,甚至在泛型约束中使用类型参数。
24 1
typeScript进阶(14)_泛型和注意事项
|
2月前
|
JavaScript 前端开发
typeScript基础(8)_ts类型断言
本文介绍了TypeScript中的类型断言,它用于在编译时告诉TypeScript某个对象具有特定的类型,即使它看起来不具备。类型断言可以用来访问一个类型上存在而另一个类型上不存在的属性或方法。需要注意的是,类型断言并不会在运行时改变JavaScript的行为,因此如果断言不当,运行时仍然可能出错。文章还提醒避免将类型断言为`any`类型或进行多重断言。
36 1
|
1月前
|
JavaScript 索引
TypeScript(TS)安装指南与基础教程学习全攻略(二)
TypeScript(TS)安装指南与基础教程学习全攻略(二)
54 0
|
1月前
|
JavaScript 前端开发 安全
TypeScript(TS)安装指南与基础教程学习全攻略(一)
TypeScript(TS)安装指南与基础教程学习全攻略(一)
29 0
|
1月前
|
JavaScript 安全 前端开发
TypeScript :枚举&字符&泛型
本文介绍了 TypeScript 中的泛型、约束、枚举和字符操作的基本用法。通过示例代码展示了如何定义和使用泛型函数、类和接口,以及如何利用 `keyof` 约束类型。此外,还介绍了枚举的定义和使用,包括常量枚举和外部枚举的区别。最后,简要说明了 `?.` 和 `??` 操作符的用途,帮助处理可能为空的属性和提供默认值。
|
2月前
|
JavaScript
typeScript进阶(13)_类与注意事项(八项特性)
TypeScript的类支持特性包括:构造函数、继承(使用`extends`)、公有/私有/受保护修饰符、只读修饰符、参数属性、存取器(getters/setters)、抽象类(用`abstract`声明)。类可用作类型。
24 0
typeScript进阶(13)_类与注意事项(八项特性)
|
1月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
48 0
下一篇
无影云桌面