本文旨在探讨TypeScript中的类、泛型的使用方法和场景,以及如何使用类型约束来增加代码的灵活性和安全性。
类的概述
在早期的JavaScript开发中(ES5)需要通过函数和原型链来实现类和继承。 从ES6开始,引入了class关键字,可以更加方便的定义和使用类。
TypeScript是JavaScript的超集,也支持使用class关键字,还支持对类的属性和方法等进行静态类型检测。
虽然在JavaScript的开发过程中,更加习惯于函数式编程,而不是面向对象编程。
React开发中,目前更多使用的函数组件以及结合Hook的开发模式
在Vue3开发中,目前也更加推崇使用Composition API
但是在封装某些业务的时候,类也具有更强大封装性。 类的定义我们通常会使用class关键字:
- 在面向对象的编程中,任何事物都可以使用类的结构来描述
- 类中可以包含一些自己特有的属性和方法
- 类也很好的诠释了面向对象的三大特性,继承、封装、多态
类的定义最基本方式
- 定一个Person 类
- 在这个类里面定义属性或者方法
- 在new 创建一个对象
- 在创建对象的时候,会执行构造器
- 通过this.name 拿到 Person 里面的那个name,然后把constrector 里面的name 赋值给this.name
/**
1.定一个Person 类
2.在这个类里面定义属性或者方法
3.在new 创建一个对象
4.在创建对象的时候,会执行构造器
5.通过this.name 拿到 Person 里面的那个name,然后把constrector 里面的name 赋值给this.name
*/
class Person{
name:string;
age:number;
constructor(name:string,age:number){
this.name=name
this.age=age
}
eat(){
console.log(this.name+"eating")
}
}
类的基本使用
const p=new Person("jiang",18)
console.log(p.name)
console.log(p.age)
应用场景
除了日常借助类的特性完成日常业务代码,还可以将类(class)也可以作为接口,尤其在 React 工程中是很常用的,如下:
export default class Carousel extends React.Component<Props, State> {}
由于组件需要传入 props 的类型 Props ,同时有需要设置默认 props 即 defaultProps,这时候更加适合使用class作为接口。
先声明一个类,这个类包含组件 props 所需的类型和初始值:
// props的类型
export default class Props {
public children: Array<React.ReactElement<any>> | React.ReactElement<any> | never[] = []
public speed: number = 500
public height: number = 160
public animation: string = 'easeInOutQuad'
public isAuto: boolean = true
public autoPlayInterval: number = 4500
public afterChange: () => {}
public beforeChange: () => {}
public selesctedColor: string
public showDots: boolean = true
}
当我们需要传入 props 类型的时候直接将 Props 作为接口传入,此时 Props 的作用就是接口,而当需要我们设置defaultProps初始值的时候,我们只需要:
public static defaultProps = new Props()
Props 的实例就是 defaultProps 的初始值,这就是 class 作为接口的实际应用,我们用一个 class 起到了接口和设置初始值两个作用,方便统一管理,减少了代码量。
什么是泛型
泛型 (Generics) 是指在定义函数、接口或类的时候,不预先指定具体的类型。而在使用的时候在指定类型的一种特性。
通俗理解:泛型就是解决 类、接口、方法 的复用性、以及对不特定数据类型的校验。
通俗的解释:
泛型是类型系统中的 参数 ,主要作用是为了类型的重用 。从上面定义可以看出:它只会用函数 接口和类中。
它和 js 程序中的函数参数是两个层面的事物 (虽然意义是相同的),因为 typescript 是静态类型系统 是在 js 进行编译时进行类型检查的系统。
因此,泛型这种参数,实际上是编译过程中的运行时使用。之所以称它为参数,是因为它具备和函数参数一模一样的特性。
function increse(param) {
// ...
}
而类型中我们如此使用泛型
function increse<T>(param: T): T{
// ...
}
当 param 为一个类型时, T 被赋值为这个类型 在返回值中 T 即为该类型从而进行类型检查。
泛型的基本使用
下面是一些常见的使用泛型的情况:
- 泛型函数:定义一个函数时,可以使用泛型来处理输入参数或返回值的类型。
function getData1(value:string):string{ //同时返回string类型和number类型(代码冗余)
return value;
}
function getData2(value:number):number{ //同时返回string类型和number类型(代码冗余)
return value;
}
function getData<T>(value:T):any{ //T 表示泛型,具体什么类型是调用这个方法的时候决定的
return 'erha';
}
getData<number>(123); //参数必须是 number
getData<string>('字符串'); //参数必须是 string
- 泛型类:类可以使用泛型来定义属性、方法或构造函数的参数。
class Minclass{ //实例中的方法参数只支持 number 类型
public list:number[] = [];
add(num:number){
this.list.push(num)
}
min():number{
var minNum = this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum > this.list[i]){
minNum = this.list[i];
}
}
return minNum;|
}
}
class Minclass<T>{ //通过泛型,可以让实例中的方法支持多种类型的参数
public list:T[] = [];
add(num:T){
this.list.push(num)
}
min():T{
var minNum = this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum > this.list[i]){
minNum = this.list[i];
}
}
return minNum;|
}
}
var ml = new Minclass<number>(); /*实例化类并且制定了类的T代表的类型是number*/
m1.add(1);
alert(ml.min());
var m2 = new Minclass<string>(); /*实例化类并且制定了类的T代表的类型是string*/
m1.add('a');
alert(ml.min());
- 泛型接口:接口也可以使用泛型来定义属性、方法或函数的类型。
interface ConfigFn{ //函数类型接口,约束类型单一
(value1:string, value2:string):string;
}
var setData:ConfigFn = function(value1:string, value2:string):string{
return value1 + value2;
}
setData('name', 'erha');
interface ConfigFn{ //泛型接口,可以允许多种类型的参数
<T>(value:T):T;
}
var getData:ConfigFn = function<T>(value:T):T{
return value;
}
getData<string>('erha');
interface ConfigFn<T>{ //也可以这样写
(value:T):T;
}
var getData:ConfigFn = function<T>(value:T):T{
return value;
}
getData<string>('erha');
需要注意的是,TypeScript 的泛型只在编译时起作用,运行时会被擦除,因为 JavaScript 是一种动态类型的语言。因此,泛型在编译阶段会进行类型检查,但在真正执行时,并不会对泛型进行运行时类型检查。
应用场景
通过上面初步的了解,后述在编写 typescript 的时候,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性的时候,这种情况下就可以使用泛型。
泛型约束
默认情况下,泛型函数的类型变量 Type 可以代表任意类型,这导致,无法访问任何属性
function id<Type>(value: Type): Type {
// 报错: 类型“Type”上不存在属性“length”
console.log(value.length)
return value
}
id<string>('a')
解释:Type 可以代表任意类型,无法保证一定存在 length 属性,比如 number 类型就没有 length
此时,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)
添加约束
比如,想要访问参数 value 的 length 属性,就可以添加以下约束:
// 创建一个接口
// interface ILength { length: number }
type Length = { length: number }
// Type extends Length 添加泛型约束
// 解释:表示传入的 类型 必须满足 Length 类型的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends Length>(value: Type): Type {
console.log(value.length)
return value
}
- 创建描述约束的接口 ILength,该接口要求提供 length 属性
- 通过 extends 关键字使用该接口,为泛型(类型变量)添加约束
- 该约束表示:传入的类型必须具有 length 属性
注意:传入的实参(比如,数组)只要有 length 属性即可(类型兼容性)
多个类型变量
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束) 比如,创建一个函数来获取对象中属性的值:
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
- 添加了第二个类型变量 Key,两个类型变量之间使用 , 逗号分隔
- keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
- 本示例中 keyof Type 实际上获取的是 person 对象所有键的联合类型,也就是:‘name’ | ‘age’
- 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
当然,第一个参数也可以添加类型约束,比如:
// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
总结
TypeScript类和泛型是开发中常用的特性,并且能够显著提升代码的可维护性和复用性。通过使用类,可以封装数据和行为,实现面向对象编程。通过使用泛型,可以延迟指定具体类型,实现代码的灵活性和复用性。