TypeScript定义接口

要想掌握typescript的知识,接口是其必经之路。很多东西都需要接触到接口,接口除了对类的一部分行为进行抽象以外,也常用于对对象的形状进行描述。接下来我们就一起来学习一下,如何才能熟练掌握接口的使用。

一. 为什么要使用接口

1.1. JavaScript存在的问题

我们在JavaScript中定义一个函数,用于获取一个用户的姓名和年龄的字符串:

const getUserInfo = function(user) {
 
return`name: ${user.name}, age: ${user.age}`
}

正确的调用方法应该是下面的方式:

getUserInfo({name: "coderwhy", age: 18})

但是当项目比较大,或者多人开发时,会出现错误的调用方法:

// 错误的调用
getUserInfo()
// Uncaught TypeError: Cannot read property 'name' of undefined
console.log(getUserInfo({name: "coderwhy"})) // name: coderwhy, age: undefined
getUserInfo({
name: "codewhy", height: 1.88}) // name: coderwhy, age: undefined

因为JavaScript是弱类型的语言,所以并不会对我们传入的代码进行任何的检测,但是在之前的javaScript中确确实实会存在很多类似的安全隐患。

如何避免这样的问题呢?

  • 当然是使用TypeScript来对代码进行重构

1.2. TypeScript代码重构一

我们可以使用TypeScript来对上面的代码进行改进:

const getUserInfo = (user: {name: string, age: number}): string => {
 
return`name: ${user.name} age: ${user.age}`;
};

正确的调用是如下的方式:

getUserInfo({name: "coderwhy", age: 18});

如果调用者出现了错误的调用,那么TypeScript会直接给出错误的提示信息:

// 错误的调用
getUserInfo();
// 错误信息:An argument for 'user' was not provided.
getUserInfo({name:
"coderwhy"}); // 错误信息:Property 'age' is missing in type '{ name: string; }'
getUserInfo({name:
"coderwhy", height: 1.88}); // 错误信息:类型不匹配

这样确实可以防止出现错误的调用,但是我们在定义函数的时候,参数的类型和函数的类型都是非常长的,代码非常不便于阅读。

所以,我们可以使用接口来对代码再次进行重构。

1.3. TypeScript代码重构二

现在我们使用接口来对user的类型进行重构。

接口重构一:参数类型使用接口定义

我们先定义一个IUser接口:

// 先定义一个接口
interface IUser {
  name:
string;
  age:
number;
}

接下来我们看一下函数如何来写:

const getUserInfo = (user: IUser): string => {
 
return`name: ${user.name}, age: ${user.age}`;
};
// 正确的调用
getUserInfo({name:
"coderwhy", age: 18}); // 错误的调用,其他也是一样
getUserInfo();

接口重构二:函数的类型使用接口定义好(后面会详细讲解接口函数的定义)

我们先定义两个接口:

  • 第二个接口定义有一个警告,我们暂时忽略它,它的目的是如果一个函数接口只有一个方法,那么可以使用type来定义
  • type IUserInfoFunc = (user: IUser) => string;
interface IUser {
  name:
string;
  age:
number;
}
interface IUserInfoFunc {
  (user: IUser):
string;
}

接着我们去定义函数和调用函数即可:

const getUserInfo: IUserInfoFunc = (user) => {
 
return`name: ${user.name}, age: ${user.age}`;
};
// 正确的调用
getUserInfo({name:
"coderwhy", age: 18}); // 错误的调用
getUserInfo();

二. 接口的基本使用

2.1. 接口的定义方式

和其他很多的语言类似,TypeScript中定义接口也是使用interface关键字来定义:

interface IPerson {
  name:
string;
}

你会发现我都在接口的前面加了一个I,这是tslint要求的,否则会报一个警告

  • 要不要加前缀是根据公司规范和个人习惯
interface name must start with a capitalized I

当然我们可以在tslint中关闭掉它:在rules中添加如下规则

"interface-name" : [true, "never-prefix"]

2.2. 接口中定义方法

定义接口中不仅仅可以有属性,也可以有方法:

interface Person {
  name:
string;
  run():
void;
  eat():
void;
}

如果我们有一个对象是该接口类型,那么必须包含对应的属性和方法:

const p: Person = {
  name:
"why",
  run() {
   
console.log("running");
  },
  eat() {
   
console.log("eating");
  },
};

2.3. 可选属性的定义

默认情况下一个变量(对象)是对应的接口类型,那么这个变量(对象)必须实现接口中所有的属性和方法。

但是,开发中为了让接口更加的灵活,某些属性我们可能希望设计成可选的(想实现可以实现,不想实现也没有关系),这个时候就可以使用可选属性(后面详细讲解函数时,也会讲到函数中有可选参数):

interface Person {
  name:
string;
  age?:
number;
  run():
void;
  eat():
void;
  study?():
void;
}

上面的代码中,我们增加了age属性和study方法,这两个都是可选的:

  • 可选属性如果没有赋值,那么获取到的值是undefined;
  • 对于可选方法,必须先进行判断,再调用,否则会报错;
const p: Person = {
  name:
"why",
  run() {
   
console.log("running");
  },
  eat() {
   
console.log("eating");
  },
};
console.log(p.age); // undefined
p.study();
// 不能调用可能是“未定义”的对象。

正确的调用方式如下:

if (p.study) {
  p.study();
}

2.4. 只读属性的定义

默认情况下,接口中定义的属性可读可写:

console.log(p.name);
p.name =
"流川枫";

如果一个属性,我们只是希望在定义的时候就定义值,之后不可以修改,那么可以在属性的前面加上一个关键字:readonly

interface Person {
  readonly name:
string;
  age?:
number;
  run():
void;
  eat():
void;
  study?():
void;
}

当我在name前面加上readonly时,赋值语句就会报错:

console.log(p.name);
p.name =
"流川枫"; // Cannot assign to 'name' because it is a read-only property.

三. 接口的高级使用

3.1. 函数类型的定义

接口不仅仅可以定义普通的对象类型,也可以定义函数的类型

// 函数类型的定义
interface SumFunc {
  (num1:
number, num2: number): number;
}
// 定义具体的函数
const sum: SumFunc = (num1, num2) => {
 
return num1 + num2;
};
// 调用函数
console.log(sum(20, 30));

不过上面的接口中只有一个函数,TypeScript会给我们一个建议,可以使用type来定义一个函数的类型:

type SumFunc = (num1: number, num2: number) =>number;

关于type的更多用户,我们后面专门进行讲解,暂时不在接口中展开讨论。

3.2. 可索引类型的定义

和使用接口描述函数的类型差不多,我们也可以使用接口来描述 可索引类型

  • 比如一个变量可以这样访问:a[3],a["name"]

可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。

// 定义可索引类型的接口
interface RoleMap {
  [index:
number]: string;
}
// 赋值具体的值
// 赋值方式一:
const roleMap1: RoleMap = {
 
0: "学生",
 
1: "讲师",
 
2: "班主任",
};
// 赋值方式二:因为数组本身是可索引的值
const roleMap2 = ["鲁班七号", "露娜", "李白"]; // 取出对应的值
console.log(roleMap1[0]); // 学生
console.log(roleMap2[1]); // 露娜

上面的案例中,我们的索引签名是数字类型,TypeScript支持两种索引签名:字符串和数字。

我们来定义一个字符串的索引类型:

interface RoleMap {
  [name:
string]: string;
}
const roleMap: RoleMap = {
  aaa:
"鲁班七号",
  bbb:
"露娜",
  ccc:
"李白",
};
console.log(roleMap.aaa);
console.log(roleMap["aaa"]); // 警告:不推荐这样来取

可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型:

  • 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。
class Person {
 
private name: string = "";
}
class Student extends Person {
 
private sno: number = 0;
}
// 下面的代码会报错
interface IndexSubject {
  [index:
number]: Person;
  [name:
string]: Student;
}

代码会报如下错误:

数字索引类型“Person”不能赋给字符串索引类型“Student”。

修改为如下代码就可以了:

interface IndexSubject {
  [index:
number]: Student;
  [name:
string]: Person;
}

下面的代码也会报错:

  • letter索引得到结果的类型,必须是Person类型或者它的子类型
interface IndexSubject {
  [index:
number]: Student;
  [name:
string]: Person;   letter: string;
}

3.3. 接口的实现

注意:在这个小节以及下一个小节中,我们会写一些类,但是目前还没有详细学习类的语法(虽然TS的类和ES6的非常相似)。

大家可以先知道我们的类如何定义,如何去和接口配合使用的即可,一些细节我会有专门的文章来解决类的使用。

”

接口除了定义某种类型规范之后,也可以和其他编程语言一样,让一个类去实现某个接口,那么这个类就必须明确去拥有这个接口中的属性和实现其方法:

  • 下面的代码中会有关于修饰符的警告,暂时忽略,后面详细讲解
// 定义一个实体接口
interface Entity {
  title:
string;
  log():
void;
}
// 实现这样一个接口
class Post implements Entity {
  title:
string;   constructor(title: string) {
   
this.title = title;
  }   log():
void {
   
console.log(this.title);
  }
}

思考:我定义了一个接口,但是我在继承这个接口的类中还要写接口的实现方法,那我不如直接就在这个类中写实现方法岂不是更便捷,还省去了定义接口?这是一个初学者经常会有疑惑的地方。

从思考方式上,为什么需要接口?

我们从生活出发理解接口

比如你去三亚/杭州旅游, 玩了一上午后饥饿难耐, 你放眼望去,会注意什么? 饭店!!

你可能并不会太在意这家饭店叫什么名字, 但是你知道只要后面有饭店两个字, 就意味着这个地方必然有饭店的实现 – 做各种菜给你吃;

接口就好比饭店/酒店/棋牌室这些名词后面添加的附属词, 当我们看到这些附属词后就知道它们具备的功能

从代码设计上,为什么需要接口?

  • 在代码设计中,接口是一种规范;
  • 接口通常用于来定义某种规范, 类似于你必须遵守的协议, 有些语言直接就叫protocol;
  • 站在程序角度上说接口只规定了类里必须提供的属性和方法,从而分离了规范和实现,增强了系统的可拓展性和可维护性;

当然,对于初次接触接口的人,还是很难理解它在实际的代码设计中的好处,这点慢慢体会,不用心急。

3.3. 接口的继承

和类相似(后面我们再详细学习类的知识),接口也是可以继承接口来提供复用性:

  • 注意:继承使用extends关键字
interface Barkable {
  barking():
void;
}
interface Shakable {
  shaking():
void;
}
interface Petable extends Barkable, Shakable {
  eating():
void;
}

接口Petable继承自Barkable和Shakable,另外我们发现一个接口可以同时继承自多个接口

如果现在有一个类实现了Petable接口,那么不仅仅需要实现Petable的方法,也需要实现Petable继承自的接口中的方法:

  • 注意:实现接口使用implements关键字
class Dog implements Petable {
  barking():
void {
   
console.log("汪汪叫");
  }   shaking():
void {
   
console.log("摇尾巴");
  }   eating():
void {
   
console.log("吃骨头");
  }
}
 

如果你觉得接口的内容就仅仅局限于此,那可就大错特错了,接口也要结合其他的知识同时运用,这其中必然少不了你反复的练习,如果你想提升你的编程能力,那就关注我,我会为你发布更多的精彩教程,帮助你突破瓶颈,提升自我。

TypeScript之路----探索接口(interface)的奥秘的更多相关文章

  1. TypeScript学习指南第二章--接口(Interface)

    接口(Interface) TypeScript的核心机制之一在于它的类型检查系统(type-checker)只关注一个变量的"模型(shape)" 稍后我们去了解这个所谓的形状是 ...

  2. typescript接口---interface

    假如我现在需要批量生产一批对象,这些对象有相同的属性,并且对应属性值的数据类型一致.该怎么去做? 在ts中,因为要检验数据类型,所以必须对每个变量进行规范,自然也提供了一种批量规范的功能.这个功能就是 ...

  3. delphi 接口Interface

    学习 delphi 接口 一切都是纸老虎!!! 第四章          接口 前不久,有位搞软件的朋友给我出了个谜语.谜面是“相亲”,让我猜一软件术语.我大约想了一分钟,猜 出谜底是“面向对象”.我 ...

  4. 使用Typescript重构axios(十一)——接口扩展

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  5. TypeScript 真香系列——接口篇

    接口带来了什么好处 好处One —— 过去我们写 JavaScript JavaScript 中定义一个函数,用来获取一个用户的姓名和年龄的字符串: const getUserInfo = funct ...

  6. TypeScript的泛型接口 泛型类接口

    /* typeScript中的泛型 泛型接口 */ //函数类型接口 /* interface ConfigFn{ (value1:string,value2:string):string; } va ...

  7. TypeScript(4)接口

    介绍 TypeScript 的核心原则之一是对值所具有的结构进行类型检查.我们使用接口(Interfaces)来定义对象的类型.接口是对象的状态(属性)和行为(方法)的抽象(描述) 接口初探 声明接口 ...

  8. java中的接口interface

    关于接口 接口描述了实现了它的类拥有什么功能.因为Java是强类型的,所以有些操作必须用接口去约束和标记.接口作为类的能力的证明,它表明了实现了接口的类能做什么. 类似与class,interface ...

  9. php中的抽象类(abstract class)和接口(interface)

    一. 抽象类abstract class 1 .抽象类是指在 class 前加了 abstract 关键字且存在抽象方法(在类方法 function 关键字前加了 abstract 关键字)的类. 2 ...

随机推荐

  1. 洛谷 P1807 最长路_NOI导刊2010提高(07) 题解

    P1807 最长路_NOI导刊2010提高(07) 题目描述 设G为有n个顶点的有向无环图,G中各顶点的编号为1到n,且当为G中的一条边时有i < j.设w(i,j)为边的长度,请设计算法,计算 ...

  2. 洛谷 P1855 榨取kkksc03 题解

    P1855 榨取kkksc03 题目描述 洛谷2的团队功能是其他任何oj和工具难以达到的.借助洛谷强大的服务器资源,任何学校都可以在洛谷上零成本的搭建oj并高效率的完成训练计划. 为什么说是搭建oj呢 ...

  3. 洛谷 4290 [HAOI2008]玩具取名 题解

    P4290 [HAOI2008]玩具取名 题目描述 某人有一套玩具,并想法给玩具命名.首先他选择WING四个字母中的任意一个字母作为玩具的基本名字.然后他会根据自己的喜好,将名字中任意一个字母用&qu ...

  4. (4)Angular的开发

    angular框架,库,是一款非常优秀的前端高级JS框架,有了这个框架就可以轻松构建SPA应用程序,通过指令宽展了HTML,通过表达式绑定数据到HTML. 轻松构建SPA应用程序,单一页面应用程序 h ...

  5. redis应用场景,缓存的各种问题

    缓存 redis还有另外一个重要的应用领域——缓存 引用来自网友的图解释缓存在架构中的位置 默认情况下,我们的服务架构如下图,客户端请求service,然后service去读取mysql数据库 问题存 ...

  6. C++2.0新特性(五)——<Rvalue_reference和move语义>

    一.Rvalue_reference(右值引用)和move语义 1.左右值概念区分 左值:表达式结束后依然存在的对象,我们也叫做变量: 右值:表达式结束后就不存在的临时对象. 2.判断左值和右值 能对 ...

  7. 中山纪中集训Day2又是测试(划水)

    A组T1 bzoj 2674 Attack Description chnlich 非常喜欢玩三国志这款游戏,并喜欢用一些策略出奇制胜.现在,他要开始征服世界的旅途了.他的敌人有N 座城市和N 个太守 ...

  8. python 通过下载包setup.py安装模块

    下载安装包,并解压到相应的位置 1.打开cmd 2.到达安装目录 3.python setup.py build 4.python setup.py install

  9. 阿里云ECS服务器环境搭建(1) —— ubuntu 16.04 图形界面的安装

    阿里云ECS服务器环境搭建(1) —— ubuntu 16.04 图形界面的安装1. 背景在我们购买阿里云ECS服务器之后,默认的系统环境是很干净的,我购买的是ubuntu16.04,远程登录进入之后 ...

  10. 范围指示器Extent Indicators

    范围指示器Extent Indicators 商务合作,科技咨询,版权转让:向日葵,135-4855__4328,xiexiaokui#qq.com   商务合作,科技咨询,版权转让:向日葵,135- ...