专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

前端开发:TypeScript 接口与泛型 接口使用泛型

ins518 2024-10-12 20:57:17 技术文章 19 ℃ 0 评论

TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6+ 的支持。TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces)来定义对象的类型。在定义函数或类时,遇到类型不明确的,可以使用泛型,泛型就是一个不确定的类型,调用时传入具体类型。本文与大家谈谈对于接口和泛型的理解。

一、接口

1. 初识接口

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

接口: 是对象的状态(属性)和行为(方法)的抽象(描述)。

需求: 创建人的对象, 需要对人的属性进行一定的约束:

/*

接口类型的对象

多了或者少了属性是不允许的

可选属性: ?

只读属性: readonly

*/

/*

需求: 创建人的对象, 需要对人的属性进行一定的约束

id是number类型, 必须有, 只读的

name是string类型, 必须有

age是number类型, 必须有

sex是string类型, 可以没有

*/

// 定义人的接口

interface IPerson {

readonly id: number;

name: string;

age: number;

sex?: string;

}

const person1: IPerson = {

id: 1,

name: "tom",

age: 20,

sex: "男",

};

2. 可选属性

接口里的属性不全都是必需的。有些是只在某些条件下存在,或者根本不存在。

// 定义人的接口

interface IPerson {

id: number;

name: string;

age: number;

sex?: string;

}

const person1: IPerson = {

id: 1,

name: "tom",

age: 20,

// sex: '男' // 可以没有

};

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ? 符号。

可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。

3. 只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。你可以在属性名前用 readonly 来指定只读属性:

// 定义人的接口

interface IPerson {

readonly id: number;

name: string;

age: number;

sex?: string;

}

const person2: IPerson = {

id: 2,

name: "tom",

age: 20,

// sex: '男' // 可以没有

// xxx: 12 // error 没有在接口中定义, 不能有

};

person2.id = 2; // error 无法为“id”赋值,因为它是只读属性。

4. 任意属性

定义了任意属性后,对象变量中的属性个数才可以出现比接口的属性数量多的情况。

// 定义人的接口

interface IPerson {

readonly id: number;

name: string;

age: number;

sex?: string;

[propName: string]: any;

}

const person2: IPerson = {

id: 2,

name: "tom",

age: 20,

test: "111",

test1: 12,

};

5. 函数类型

接口能够描述 JavaScript 中对象拥有的各种各样的外形。除了描述带有属性的普通对象外,接口也可以描述函数类型。

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

/*

接口可以描述函数类型(参数的类型与返回的类型)

*/

interface SearchFunc {

(source: string, subString: string): boolean;

}

/*

这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。

下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。

*/

const mySearch: SearchFunc = function (source: string, sub: string): boolean {

return source.search(sub) > -1;

};

console.log(mySearch("abcd", "bc"));

接口能够描述 JavaScript 中对象拥有的各种各样的外形。除了描述带有属性的普通对象外,接口也可以描述函数类型。

6. 接口定义多次产生的效果

在TS中,接口是可以多次声明的(声明同样名字的接口)。TS编译器会将名字相同的多个声明合并为一个声明。合并后的声明同时拥有多个声明的特性。

// 定义人的接口

interface IPerson {

readonly id: number;

age: number;

}

interface IPerson {

name: string;

sex: string;

}

const person2: IPerson = {

//报错

id: 2,

name: "tom",

age: 20,

};

会有报错信息:Property 'sex' is missing in type '{ id: number; name: string; age: number; }' but required in type 'IPerson'.

7. 类类型

类实现接口,与 C# 或 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约。

/*

类类型: 实现接口

1. 一个类可以实现多个接口

2. 一个接口可以继承多个接口

*/

interface Alarm {

alert(): any;

}

interface Light {

lightOn(): void;

lightOff(): void;

}

class Car implements Alarm {

alert() {

console.log("Car alert");

}

}

8. 一个类可以实现多个接口

class Car2 implements Alarm, Light {

alert() {

console.log("Car alert");

}

lightOn() {

console.log("Car light on");

}

lightOff() {

console.log("Car light off");

}

}

9. 接口继承接口

和类一样,接口也可以相互继承。这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

interface LightableAlarm extends Alarm, Light {

}

二、泛型

1. 初识泛型

指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。

下面创建一个函数, 实现功能: 根据指定的数量 count 和数据 value , 创建一个包含 count 个 value 的数组 不用泛型的话,这个函数可能是下面这样:

function createArray(value: any, count: number): any[] {

const arr: any[] = [];

for (let index = 0; index < count; index++) {

arr.push(value);

}

return arr;

}

const arr1 = createArray(11, 3);

const arr2 = createArray("aa", 3);

console.log(arr1[0].toFixed(), arr2[0].split(""));

2. 使用函数泛型

function createArray2<T>(value: T, count: number) {

const arr: Array<T> = [];

for (let index = 0; index < count; index++) {

arr.push(value);

}

return arr;

}

const arr3 = createArray2<number>(11, 3);

console.log(arr3[0].toFixed());

// console.log(arr3[0].split('')) // error

const arr4 = createArray2<string>("aa", 3);

console.log(arr4[0].split(""));

// console.log(arr4[0].toFixed()) // error

3. 多个泛型参数的函数

一个函数可以定义多个泛型参数

function swap<K, V>(a: K, b: V): [K, V] {

return [a, b];

}

const result = swap<string, number>("abc", 123);

console.log(result[0].length, result[1].toFixed());

4. 泛型接口

在定义接口时, 为接口中的属性或方法定义泛型类型。

在使用接口时, 再指定具体的泛型类型。

  1. 声明泛型类型: 在接口名的右侧 <T> // T K V
  2. 使用泛型: 接口体中
  3. 指定泛型的具体类型: 定义实现时, 接口名的右侧 <具体类型>

// 定义一个类,这个类是专门对数据进行增删改查的工具类

// 可以实例化一个对象用来保管数据

// 保管数据的话 需要一个接口限定

class User {

id: number;

name: string; //姓名

age: number; //年龄

constructor(name, age, id) {

this.id = id;

this.name = name;

this.age = age;

}

}

interface IbaseCRUD<T> {

data: T[];

add: (t: T) => void;

getById: (id: number) => T;

}

// 管理工具类

// 实例化这个类我就可以拿到一个工具对象用来管理东西

class UserCRUD implements IbaseCRUD<User> {

data: User[] = [];

add(u: User): void {

this.data.push(u);

}

getById(id: number) {

return this.data.find((item: User) => item.id === id);

}

}

let a1 = new UserCRUD();

let u1 = new User("zhaoliying", 35, 1);

let u2 = new User("yangmi", 36, 2);

a1.add(u1);

a1.add(u2);

console.log(a1.getById(1));

5. 泛型类

正文部分文本字体大小为15px可以插入代码 段后距24正文部分文本字体大小为15px可以插入代码 段后距24

1)声明泛型类型: 在类名的右侧 <T> // T K V

2)使用泛型: 类体中

3)指定泛型的具体类型: 创建类的实例时, 类名的右侧 <具体类型>

class GenericData<T> {

zeroValue: T;

add: (x: T, y: T) => T;

}

const genericNumber = new GenericData<number>();

genericNumber.zeroValue = 4;

genericNumber.add = function (x, y) {

return x + y;

};

console.log(genericNumber.add(genericNumber.zeroValue, 5));

let genericString = new GenericData<string>();

genericString.zeroValue = "abc";

genericString.add = function (x, y) {

return x + y;

};

console.log(genericString.add(genericString.zeroValue, "test"));

6. 泛型约束

如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性。

// 没有泛型约束

function fn<T>(x: T): void {

// console.log(x.length) // error

}

我们可以使用泛型约束来实现:

interface Lengthwise {

length: number;

}

// 指定泛型约束

function fn2<T extends Lengthwise>(x: T): void {

console.log(x.length);

}

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

fn2("abc");

// fn2(123) // error number没有length属性

7. 泛型工具

?条件判断

条件判断会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一。

T extends U ? X : Y

上述代码含义为:如果 T 包含的类型是 U 包含的类型的 '子集',那么取结果 X,否则取结果 Y。

实现一个简单的示例代码:

type WhatType<T> = T extends null | undefined ? never : T

let typeString: WhatType<string> = 'abc' // string 类型

let typeNull: WhatType<null> // never 类型

typeof

typeof 操作符用来在类型上下文中获取变量或者属性的类型。示例代码如下:

interface IPerson {

name: string;

age: number;

}

const user: IPerson = {

name: "jenny",

age: 18,

};

type student = typeof user; // IPerson

keyof

keyof 操作符用来获取某种类型的所有 key 值,返回一个联合类型。示例代码如下:

interface IPerson {

name: string;

age: number;

}

type allKey1 = keyof IPerson; // 'name' | 'age'

type allKey2 = keyof IPerson[]; // 'length | 'toString | 'pop' | 'push' | 'concat' | 'join' | ......

type allKey3 = keyof { [x: string]: IPerson }; // string | number

in

in 操作符用来遍历枚举类型。示例代码如下:

type keys = "a" | "b" | "c";

type obj = {

[p in keys]: any;

}; // { a: any, b: any, c: any}

infer

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

infer 可以在 extends 的条件语句中推断待推断的类型。示例代码如下:

type ParamType<T> = T extends (...args: infer P) => any ? P : T;

interface User {

name: string;

age: number;

}

type Func = (user: User) => void;

type Param = ParamType<Func>; // Param = User

type AA = ParamType<string>; // string

Required

Required<T> 的作用就是将某个类型中的属性全部变为必选。

type Required<T> = {

[P in keyof T]-?: T[P];

};

-? 的作用就是把可选属性的可选性去掉,使该属性变成必选项。示例代码如下:

interface IPerson {

name?: string;

age?: number;

}

type person = Required<IPerson>;

// 相当于

// type person = {

// name: string;

// age: number;

// }

Readonly

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

type Readonly<T> = {

readonly [P in keyof T]: T[P];

};

在每一个 key 前面加上 readonly。示例代码如下:

interface IPerson {

name: string;

age: number;

}

type person = Readonly<IPerson>;

// 相当于

// type person = {

// readonly name: string;

// readonly age: number;

// }

const obj: person = {

name: "lucy",

age: 18,

};

// 此处 TS 会报错:无法为 name 重新赋值,因为它是只读属性

obj.name = "jenny";

三、TypeScript中常用内置工具类型

TS中常用的工具类型,让写TS时效率大大提升,避免无意义的重复性定义。

1. Omit 省略/剔除

可以剔除已定义对象中,自己不需要的一部分形成新的定义类型。

interface UserObj {

readonly name: string; // readonly 只读属性 只能初始化定义 不能二次赋值

age: number;

id: number;

sex: 0 | 1;

address: string;

weight: number;

}

// 剔除省略自己不需要的

type Person = Omit<UserObj, "number" | "sex" | "address" | "weight">;

// 此时Person 等同于 Person1

interface Person1 {

readonly name: string;

id: number;

}

2. Pick 采集

可以采集已定义对象中,自己需要的一部分形成新的定义类型。

interface UserObj {

readonly name: string;

age: number;

id: number;

sex: 0 | 1;

address: string;

weight: number;

}

// 采集需要的

type Person = Pick<UserObj, "name" | "id">;

// 此时Person 等同于 Person1

interface Person1 {

readonly name: string;

id: number;

}

总结

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型,接口: 是对象的状态(属性)和行为(方法)的抽象(描述)。接口内部不能写属性的值和方法的实现,只是声明一下。接口是用来限制对象数据,是一个规范。泛型指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表