首页 前端知识 TypeScript - Interfaces(接口)

TypeScript - Interfaces(接口)

2024-05-05 22:05:27 前端知识 前端哥 508 809 我要收藏

目录

1、接口介绍

1.1 接口示例

2、可选属性

3、只读属性

4、额外的属性检查

5、函数类型

6、可索引的类型

7、类类型

7.1 类静态部分和实例部分

8、继承接口

9、混合类型

10、接口继承类


1、接口介绍

TypeScript 的核心原则之一是对值所具有的_结构_进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

1.1 接口示例

接口声明是命名对象类型的另一种方法,看下示例:

function people(obj: { name: string, age: number }) {
  console.log(obj.name+ '年龄是:'+obj.age);
}
let obj = { age: 20, name: '张三', addr: '北京市' }; 
people(obj)

// 张三年龄是:20

在这里people方法中,可以传入一个对象,对象必须一个name属性,和一个age属性,就是在传入传参数的时候,如果直接传递的是一个对象,那么这个对象一定是方法声明对象的父集,就是方法people中对象的属性,在传入对象中都能找到,且对应属性类型必须一致。

看另一种写法:

function people(obj: { name: string, age: number }) {
  console.log(obj.name+ '年龄是:'+obj.age);
}
people({age:20, name: 'z', addr: '北京市'})

// 对象字面量只能指定已知属性,并且“addr”不在类型“{ name: string; age: number; }”中。

当然,我们事先定义接口,或者类型别名也是可以的,如下所示:

interface MyInterface {
  name: string,
  age: number
}
type MyType = {
  name: any,
  age: number,
  addr: '北京市'
}

function people(obj: MyInterface | MyType) {
  console.log(obj.name+ '年龄是:'+obj.age);
}
let myObj = {age:20, name: '张三', addr: '北京市'}
people(myObj)

// 张三年龄是:20

只要MyInterface 和Mytype同时都是name和age属性就可以,因为检查器会逐个检查每个属性是否存在,以及它们对应的类型,类型必须小于等于定义的类型即可。在这里不像其他的面向对象语言,例如:Java,向他们那样必须去实现某个接口,它们这些是编译型语言,在编译的是否会有严格的限制,JavaScript属于解释性语言,限制没那么严格,而TypeScript是JavaScript的超集,自然也遵从这个规定,只要传入的接口或类型别名有对应的属性就行,而这也是TypeScript有时被称做“鸭式辨型法”的原因。

鸭式辨型来自于James Whitecomb Riley的名言:"像鸭子一样走路并且嘎嘎叫的就叫鸭子。

2、可选属性

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用“option bags”模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。

下面是一个示例:

interface MyInterface {
  name: string,
  age: number
  addr?: string
}

function people(obj: MyInterface) {
  console.log(obj.name+ '年龄是:'+obj.age);
}
let myObj = {age: 20, name: '张三'}
people(myObj)

可选属性,就是在属性后面加了个问号(?)所以添加问号的属性,都是非必填属性。

可选属性的好处就是可以进行预定义,后续可以根据实际情况去判断该属性是否存在,当然这种方式还有一种好处,在写代码的时候可以自动进行智能提示,找到对应的属性。

如下所示:

 这样也可以防止我们写错属性而没有被发现。

3、只读属性

在声明对象属性的时候,可以在属性名之前添加 readonly 来指定是只读属性。

interface People = {
    readonly name: string,
    readonly age: number
}
let p1:People = {name: '张三', age: 12}
p1.age = 24; // 编译报错 无法为“age”赋值,因为它是只读属性。

 对象被重新构建之后,所有的属性都不能变更了。

ReadonlyArray<T>类型,可以保证数组创建之后也不能进行变更了,可读数组类型,实在ES2019(ES10)之后所添加,如下所示:

let numArr:number[] = [10, 30, 50, 70, 90];
let readonlyArr:ReadonlyArray<number> = numArr;
readonlyArr[0] = 123; // 类型“readonly number[]”中的索引签名仅允许读取。
readonlyArr.push(123) // 类型“readonly number[]”上不存在属性“push”
readonlyArr.splice(1); // 属性“splice”在类型“readonly number[]”上不存在
readonlyArr.length = 1  // 无法为“length”赋值,因为它是只读属性。
numArr = readonlyArr  // 不能分配给可变类型 "number[]"

4、额外的属性检查

在对象中指定了可选属性,如果传入的是一个对象字面量,而这个对象字面量中的属性,和之前对象定义的属性不一致时,会编译报错,说对象字面量只能指定已知的属性。

例如:那属性name写成names时,如下所示:

interface MyInterface {
    name?: string,
    age?: number
    addr?: string
  }
  
  function people(obj: MyInterface) {
    console.log(obj.name+ '年龄是:'+obj.age);
  }
  people({age: 20, names: '张三'})
 // 类型“{ age: number; names: string; }”的参数不能赋给类型“MyInterface”的参数。

如果跳过编译检查,有以下两种方式:

  // 第一种方式,使用类型断言
  people({age: 20, names: '张三'} as MyInterface)
  // 第二种方式,把字面量对象赋值给一个变量。
  let obj = {age: 20, names: '张三'}
  people(obj)

第二种方式,虽然能跳转编译检查,但可能会产生bug,虽然能运行,但是结果会与预期不一样,所以,最好能提前规避,在编译期间找出对应的bug.

如果我们确定对象可能会有多个未知的属性,我们可以添加字符串索引签名,类似于在JavaScript函数,传参一般(...obj),说明该函数接收的参数可能还有其他多个。

interface MyInterface {
    name?: string,
    age?: number,
    addr?: string,
    [other:string]: any
}

5、函数类型

我们可以使用接口表示函数类型,参数属性和之前对象属性定义一样,末尾再加上函数的返回类型,如下所示:

interface MyInterface {
    (name: string, age: number, addr?:string) :string
  }
  
let p1:MyInterface;  
p1 = function(name:string, age:number, addr?:string) {
    return name+ '年龄是:'+age
}
console.log('p1: ', p1("张三", 20));

对象函数参数定义和JavaScript中一样,名字不一定要和接口里定义的一样,函数中的参数,与传参顺序有关,所以在这里,实际传的值与函数参数顺序对应起来就可以了。

如果函数中没有指定参数类型,会根据接口定义进行自动推断。

interface MyInterface {
    (name: string, age: number, addr?:string) :string
  }
  
let p1:MyInterface;  
p1 = function(a, b, addr) {
    return a+ '年龄是:'+b
}
console.log('p1: ', p1("张三", 20));

6、可索引的类型

我们可以使用索引访问类型在另一种类型上查找特定属性:

type Person = { age: number; name: string; alive: boolean };
let p1: Person;
p1= {age: 10, name: '张三', alive: true}
console.log('Person["age"]: ', p1["age"]);
// Person["age"]:  10     

索引类型本身就是一个类型,因此我们可以完全使用联合、 或其他类型的类型:keyof

type Person = { age: number; name: string; alive: boolean };

// type I1 = string | number
type I1 = Person["age" | "name"];     

// type I2 = string | number | boolean
 
type I2 = Person[keyof Person];
     
type AliveOrName = "alive" | "name";
// type I3 = string | boolean
type I3 = Person[AliveOrName];

如果您尝试为不存在的属性编制索引,您甚至会看到错误:

type Person = { age: number; name: string; alive: boolean };

// 编译报错,类型“Person”上不存在属性“alve”。
type I1 = Person["alve"];

用于获取数组元素的类型,我们可以将其方便地捕获数组对应的元素类型:number typeof

const MyArray = [
    { name: "Alice", age: 15 },
    { name: "Bob", age: 23 },
    { name: "Eve", age: 38 },
  ];
   
// type Person = {
//     name: string;
//     age: number;
// }  
type Person = typeof MyArray[number];

// type Age = numbers
type Age = typeof MyArray[number]["age"];     
// Or
type Age2 = Person["age"];

只能在索引时使用类型,这意味着不能使用 进行变量引用:const

const key = "age";
//报错,类型“key”不能作为索引类型使用。
type Age = Person[key];
// 可以
type Age1 = Person['age'];

用类型别名是可以的。

type key = "age";
// 可以 type Age = number
type Age = Person[key];

7、类类型

C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。也就是面对对象编程中类的继承概念,定义一个接口,由类去实现这个接口。

interface animal {
    birthday: Date
    getInfo(): string
}

class Cat implements animal {
    birthday: Date;
    name: string;
    getInfo() :string {
        return this.name + ", " + this.birthday;
    }
    constructor(name: string, birthday: Date) {
        this.name = name;
        this.birthday = birthday;
    }
}

 let c1 = new Cat('叮当猫', new Date('2023-05-09'));
 console.log(c1.getInfo());
 // 叮当猫, Tue May 09 2023 08:00:00 GMT+0800 (China Standard Time)

7.1 类静态部分和实例部分

在操作类和接口的时候,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:

interface Animal {
    birthday: Date
    new(name: string, birthday: Date) 
}
// 编译错误,类型“Cat”提供的内容与签名“new (name: string, birthday: Date): any”不匹配。
class Cat implements Animal {
    birthday: Date;
    name: string;
    getInfo() :string {
        return this.name + ", " + this.birthday;
    }
    constructor(name: string, birthday: Date) {
        this.name = name;
        this.birthday = birthday;
    }
}

所以,在类的静态部分我们做一些改变,我们再增加一个接口AnimalInter,然后把原接口构造器添加一个返回值为AnimalInter接口,新建一个函数,函数的返回类型为AnimalInter接口,而函数的第一个参数进行实例化,如下图所示:

interface Animal {
    new(name: string, birthday: Date): AnimalInter 
}

interface AnimalInter {
    getInfo() : string
}

function CreateFunc(first:Animal, name: string, birthday: Date ):AnimalInter {
    return new first(name, birthday)
}

class Cat{ 
    birthday: Date;
    name: string;
    getInfo() {
        return this.name + ", " + this.birthday;
    }
    constructor(name: string, birthday: Date) {
        this.name = name;
        this.birthday = birthday;
    }
}

let result = CreateFunc(Cat, '张三', new Date('2023-02-09'))
console.log('result: ', result.getInfo());

这样在示例化这个Cat类的时候,检查是否和Animal接口构造函数定义的一致。

8、继承接口

接口的继承和类是一样的,是可以相互继承的,这样可以进行拆分多个接口,便于以后的可复用,重构起来也更方便,如下所示:

interface Animal {
    name: string,
}

interface AnimalInter extends Animal {
    getInfo() : string
}

class Cat implements AnimalInter{ 
    birthday: Date;
    name: string;
    getInfo() {
        return this.name + ", " + this.birthday;
    }
    constructor(name: string, birthday: Date) {
        this.name = name;
        this.birthday = birthday;
    }
}

let result = new Cat('张三', new Date('2023-02-09'))
console.log('result: ', result.getInfo());

一个接口可以继承多个接口,创建出多个接口的合成接口。

interface Animal {
    name: string,
}
interface AnimalSecond {
    age: number,
}

interface AnimalInter extends Animal, AnimalSecond {
    getInfo() : string
}

class Cat implements AnimalInter{ 
    birthday: Date;
    name: string;
    age: number;
    getInfo() {
        return this.name + ", " + this.birthday + ", age: "+this.age;
    }
    constructor(name: string, birthday: Date, age: number) {
        this.name = name;
        this.birthday = birthday;
        this.age = age;
    }
}

let result = new Cat('张三', new Date('2023-02-09'), 20)
console.log('result: ', result.getInfo());

9、混合类型

混合类型,一个变量它的类型可能有多个,number | string| boolean 等,一个对象也可能是,以下就是一个对象,还可以是一个函数。

interface Animal {
    (age: number): string;
    name: string;
    time: number;
}

function getAnimal (): Animal {
    let animal = function(age: number) {} as Animal
    return animal;
}
let getResult = getAnimal();
getResult(20);
getResult.time = 20;
// 报错,无法为“name”赋值,因为它是只读属性。函数的name,是可读的
getResult.name = '张三';
console.log(getResult.time);
console.log(getResult.name ); 

10、接口继承类

接口继承类时,会继承类的成员,不包括对应的实现。接口可以继承类的私有成员,和受保护的成员,如果需要继承这个接口,只能由接口继承类的子类继承。在构造器函数中,子类的构造函数必须包含 "super" 的调用。

class AnimalClass {
   private name: any;
}

interface AnimalInterface extends AnimalClass{
    getName(): string; 
}

class Animal extends AnimalClass implements AnimalInterface{
    getName() {
        return '123';
    };
    constructor() {
       super();
    }
}

// 类型 "Cat" 中缺少属性 "name",但类型 "AnimalInterface" 中需要该属性。
class Cat implements AnimalInterface {
    getName() {
        return ''
    }
}

let anima = new Animal()

Animal类继承AnimalClass类,在构造器中,必须调用super() 方法,子类进行初始化的时候,父类中有定义成员属性也需进行初始化操作,而AnimalInterface接口只能被AnimalClass类的子类所实现,因为本身接口就继承了AnimalClass类,而Cat在实现AnimalInterface接口时,必须包含接口从其他类中继承的所有属性,包括私有属性,Cat类不是AnimalInterface接口继承类的子类,所以就报错了,而父类私有属性不能通过子类实例化后的对象进行直接访问,也不会被继承。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/7070.html
标签
评论
发布的文章

HTML5-新增表单元素

2024-05-10 08:05:59

Dayjs 的一些常用方法

2024-05-10 08:05:59

Howler.js HTML5声音引擎

2024-05-10 08:05:59

前端攻城狮HTML5自查手册

2024-05-10 08:05:51

JavaScript 基础入门

2024-05-10 08:05:41

HTML5新手入门指南

2024-05-08 10:05:28

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!