接口带来了什么好处

好处One —— 过去我们写 JavaScript

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

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

函数调用:

  1. getUserInfo({name: "koala", age: 18})
  1.  

这对于我们之前在写 JavaScript 的时候,再正常不过了,但是如果这个 getUserInfo 在多人开发过程中,如果它是个公共函数,多个开发者都会调用,如果不是每个人点进来看函数对应注释,可能会出现以下问题:

  1. // 错误的调用
  2. getUserInfo() // Uncaught TypeError: Cannot read property 'name' of undefined
  3. console.log(getUserInfo({name: "kaola"})) // name: kaola, age: undefined
  4. getUserInfo({name: "kaola", height: 1.66}) // name: koala, age: undefined
  1.  

JavaScript 是弱类型的语言,所以并不会对我们传入的代码进行任何的检测,有些错你自己都说不清楚,但是就出了问题。

TypeScript 中的 interface 可以解决这个问题

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

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

  1. getUserInfo({name: "kaola", age: 18});
  1.  

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

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

这时候你会发现这段代码还是有点长,代码不便与阅读,这时候就体现了 interface 的必要性。

使用 interface 对 user 的类型进行重构。

我们先定义一个 IUser 接口:

  1. // 先定义一个接口
  2. interface IUser {
  3. name: string;
  4. age: number;
  5. }
  1.  

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

  1. const getUserInfo = (user: IUser): string => {
  2. return `name: ${user.name}, age: ${user.age}`;
  3. };
  4.  
  5. // 正确的调用
  6. getUserInfo({name: "koala", age: 18});
  1.  

// 错误的调用和之前一样,报错信息也相同不再说明。

接口中函数的定义再次改造

定义两个接口:

  1. type IUserInfoFunc = (user: IUser) => string;
  2.  
  3. interface IUser {
  4. name: string;
  5. age: number;
  6. }
  1.  

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

  1. const getUserInfo: IUserInfoFunc = (user) => {
  2. return `name: ${user.name}, age: ${user.age}`;
  3. };
  1.  

// 正确的调用

  1. getUserInfo({name: "koala", age: 18});
  1.  

// 错误的调用

  1. getUserInfo();
  1.  

好处TWO —— 过去我们用 Node.js 写后端接口

其实这个说明和上面类似,我再提一下,就是想证明 TypeScript 确实挺香的! 写一个后端接口,我要特意封装一个工具类,来检测前端给我传递过来的参数,比如下图中的validate专门用来检验参数的函数

但是有了 TypeScript 这个参数检验函数可以省略了,我们可以这样写:

  1. const goodParams: IGoodsBody = this.ctx.body;

GoodsBody就是对应参数定义的 interface,比如这个样子

  1. // -- 查询列表时候使用的接口
  2. interface IQuery {
  3. page: number;
  4. rows: number;
  5. disabledPage?: boolean; // 是否禁用分页,true将会忽略`page`和`rows`参数
  6. }
  7. // - 商品
  8. export interface IGoodsQuery extends Query {
  9. isOnline?: string | number; // 是否出售中的商品
  10. goodsNo?: string; // 商品编号
  11. goodsName?: string; // 商品名称
  12. }
  1.  

接口的基础篇

接口的定义

和 java 语言相同,TypeScript 中定义接口也是使用 interface 关键字来定义:

  1. interface IQuery {
  2. page: number;
  3. }
  1.  

你会发现我都在接口的前面加了一个I,算是个人习惯吧,之前一直写 java 代码,另一方面tslint要求,否则会报一个警告,是否加看个人。

接口中定义方法

看上面的接口中,我们定义了 page 常规属性,定义接口时候不仅仅可以有 属性,也可以有方法,看下面的例子:

  1. interface IQuery {
  2. page: number;
  3. findOne(): void;
  4. findAll(): void;
  5. }
  1.  

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

  1. const q: IQuery = {
  2. page: 1,
  3. findOne() {
  4. console.log("findOne");
  5. },
  6. findAll() {
  7. console.log("findAll");
  8. },
  9. };
  1.  

接口中定义属性

普通属性

上面的 page 就是普通属性,如果有一个对象是该接口类型,那么必须包含对应的普通属性。就不具体说了。

可选属性

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

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

  1. interface IQuery {
  2. page: number;
  3. findOne(): void;
  4. findAll(): void;
  5. isOnline?: string | number; // 是否出售中的商品
  6. delete?(): void
  7. }
  1.  

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

注意:可选属性如果没有赋值,那么获取到的值是undefined; 对于可选方法,必须先进行判断,再调用,否则会报错;

  1. const q: IQuery = {
  2. page: 1,
  3. findOne() {
  4. console.log("findOne");
  5. },
  6. findAll() {
  7. console.log("findAll");
  8. },
  9. };
  10.  
  11. console.log(p.isOnline); // undefined
  12. p.delete(); // 不能调用可能是“未定义”的对象。
  1.  

正确的调用方式如下:

  1. if (p.delete) {
  2. p.delete();
  3. }

大家可能会问既然是可选属性,可有可无的,那么为什么还要定义呢?对比起完全不定义,定义可选属性主要是:为了让接口更加的灵活,某些属性我们可能希望设计成可选,并且如果存在属性,能约束类型,而这也是十分关键的。

只读属性

默认情况下,接口中定义的属性可读可写: 但是有一个关键字 readonly,定义的属性值,不可以进行修改,强制修改后报错。

  1. interface IQuery {
  2. readonly page: number;
  3. findOne(): void;
  4. }
  1.  

page属性加了readonly关键字,再给它赋值会报错。

  1. const q: IQuery = {
  2. page: 1,
  3. findOne() {
  4. console.log("findOne");
  5. },
  6. };
  7. q.page = 10;// Cannot assign to 'page' because it is a read-only property.
  1.  

接口的高级篇

函数类型接口

Interface 还可以用来规范函数的形状。Interface 里面需要列出参数列表返回值类型的函数定义。写法如下:

  • 定义了一个函数接口
  • 接口接收三个参数并且不返回任何值
  • 使用函数表达式来定义这种形状的函数
  1. interface Func {
  2. // ✔️ 定于这个函数接收两个必选参数都是 number 类型,以及一个可选的字符串参数 desc,这个函数不返回任何值
  3. (x: number, y: number, desc?: string): void
  4. }
  5.  
  6. const sum: Func = function (x, y, desc = '') {
  7. // const sum: Func = function (x: number, y: number, desc: string): void {
  8. // ts类型系统默认推论可以不必书写上述类型定义
  9. console.log(desc, x + y)
  10. }
  11.  
  12. sum(32, 22)
  1.  

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

  1. type Func = (x: number, y: number, desc?: string) => void;
  1.  

接口的实现

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

下面的代码中会有关于修饰符的警告,暂时忽略,后面详细讲解 // 定义一个实体接口

  1. interface Entity {
  2. title: string;
  3. log(): void;
  4. }
  1.  

// 实现这样一个接口

  1. class Post implements Entity {
  2. title: string;
  3.  
  4. constructor(title: string) {
  5. this.title = title;
  6. }
  7.  
  8. log(): void {
  9. console.log(this.title);
  10. }
  11. }
  1.  

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

解答这个疑惑之前,先记住两个字,规范!

这个规范可以达到你一看这名字,就知道他是用来干什么的,并且可拓展,可以维护。

  • 代码设计中,接口是一种规范; 接口通常用于来定义某种规范, 类似于你必须遵守的协议,

  • 站在程序角度上说接口只规定了类里必须提供的属性和方法,从而分离了规范和实现,增强了系统的可拓展性和可维护性;

接口的继承

和类一样,接口也能继承其他的接口。这相当于复制接口的所有成员。接口也是用关键字 extends 来继承。

  1. interface Shape { //定义接口Shape
  2. color: string;
  3. }
  4.  
  5. interface Square extends Shape { //继承接口Shape
  6. sideLength: number;
  7. }
  1.  

一个 interface 可以同时继承多个 interface ,实现多个接口成员的合并。用逗号隔开要继承的接口。

  1. interface Shape {
  2. color: string;
  3. }
  4.  
  5. interface PenStroke {
  6. penWidth: number;
  7. }
  8.  
  9. interface Square extends Shape, PenStroke {
  10. sideLength: number;
  11. }
  1.  

  需要注意的是,尽管支持继承多个接口,但是如果继承的接口中,定义的同名属性的类型不同的话,是不能编译通过的。如下代码:

  1. interface Shape {
  2. color: string;
  3. test: number;
  4. }
  5.  
  6. interface PenStroke extends Shape{
  7. penWidth: number;
  8. test: string;
  9. }
  1.  

另外关于继承还有一点,如果现在有一个类实现了 Square 接口,那么不仅仅需要实现 Square 的方法,也需要实现 Square 继承自的接口中的方法,实现接口使用 implements 关键字 。

可索引类型接口

interface和type的区别

type 可以而 interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型
  1. // 基本类型别名
  2. type Name = string
  3.  
  4. // 联合类型
  5. interface Dog {
  6. wong();
  7. }
  8. interface Cat {
  9. miao();
  10. }
  11.  
  12. type Pet = Dog | Cat
  13.  
  14. // 具体定义数组每个位置的类型
  15. type PetList = [Dog, Pet]
  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值
  1. // 当你想获取一个变量的类型时,使用 typeof
  2.  
  3. let div = document.createElement('div');
  4. type B = typeof div
  • type 其他骚操作
  1. type StringOrNumber = string | number;
  2. type Text = string | { text: string };
  3. type NameLookup = Dictionary<string, Person>;
  4. type Callback<T> = (data: T) => void;
  5. type Pair<T> = [T, T];
  6. type Coordinates = Pair<number>;
  7. type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

interface 可以而 type 不行

interface 能够声明合并

  1. interface User {
  2. name: string
  3. age: number
  4. }
  5.  
  6. interface User {
  7. sex: string
  8. }
  9.  
  10. /*
  11. User 接口为 {
  12. name: string
  13. age: number
  14. sex: string
  15. }
  16. */
  1.  

另外关于type的更多内容,可以查看文档:TypeScript官方文档

接口的应用场景总结

在项目中究竟怎么用,开篇已经举了两个例子,在这里再简单写一点,最近尝试了一下egg+ts,学习下。在写查询参数检验的时候,或者返回固定数据的时候,都会用到接口,看一段简单代码,已经看完了上面的文章,自己体会下吧。

  1. import User from '../model/user';
  2. import Good from '../model/good';
  3.  
  4. // 定义基本查询类型
  5. // -- 查询列表时候使用的接口
  6. interface Query {
  7. page: number;
  8. rows: number;
  9. disabledPage?: boolean; // 是否禁用分页,true将会忽略`page`和`rows`参数
  10. }
  11.  
  12. // 定义基本返回类型
  13. type GoodResult<Entity> = {
  14. list: Entity[];
  15. total: number;
  16. [propName: string]: any;
  17. };
  18.  
  19. // - 商品
  20. export interface GoodsQuery extends Query {
  21. isOnline?: string | number; // 是否出售中的商品
  22. goodsNo?: string; // 商品编号
  23. goodsName?: string; // 商品名称
  24. }
  25. export type GoodResult = QueryResult<Good>;

作者:ikoala
链接:https://juejin.im/post/5dd1098e51882529f21587db
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

TypeScript 真香系列——接口篇的更多相关文章

  1. 真香系列之 Golang 升级

    Golang 以前的依赖管理一直饱受诟病,社区的方案也层出不穷,比如 vendor, glide, godep 等.之前的依赖管理一直是依靠 GOPATH 或者将依赖代码下载到本地,这种方式都有劣势. ...

  2. JavaFX桌面应用-MVC模式开发,“真香”

    使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...

  3. 你只会用 StringBuilder?试试 StringJoiner,真香!

    你只会用 StringBuilder/ StringBuffer 拼接字符串? 那你就 OUT 了!! 如果需要拼接分隔符的字符串,建议使用 Java 8 中的这款拼接神器:StringJoiner, ...

  4. 联合迭代器与生成器,enumerate() 内置函数真香!

    花下猫语:Python 中很多内置函数的作用都非常大,比如说 enumerate() 和 zip(),它们使得我们在作迭代操作时极为顺手.这是一篇很多年前的 PEP,提议在 Python 2.3 版本 ...

  5. 阿里神器 Seata 实现 TCC模式 解决分布式事务,真香!

    今天这篇文章介绍一下Seata如何实现TCC事务模式,文章目录如下: 什么是TCC模式? TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交.是目前最火的一种柔性事务 ...

  6. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  7. (Hibernate进阶)Hibernate系列——总结篇(九)

    这篇博文是hibernate系列的最后一篇,既然是最后一篇,我们就应该进行一下从头到尾,整体上的总结,将这个系列的内容融会贯通. 概念 Hibernate是一个对象关系映射框架,当然从分层的角度看,我 ...

  8. Java多线程系列--“基础篇”02之 常用的实现多线程的两种方式

    概要 本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable.之所以说是常用的,是因为通过还可以通过java.util.concurrent包中的线程池来实现多线程.关于线程 ...

  9. Java多线程系列--“基础篇”05之 线程等待与唤醒

    概要 本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. wait()和notify()3. wait(long t ...

随机推荐

  1. Spring Security 解析(五) —— Spring Security Oauth2 开发

    Spring Security 解析(五) -- Spring Security Oauth2 开发   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决 ...

  2. React学习笔记①

    三种导出方式 export let num = 1://1 let num2 = 2://2 export {num2};//2 export default {default}//3 三种导入方式 ...

  3. Java 基本类型包装类Wrapper

    一.包装类概述 1.为什么需要包装类 Java并不是纯面向对象的语言.Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的.基本数据类型有它的优势:性能(效率高,节省空间). ...

  4. linux rwx 权限说明

    Linux的文件和目录的权限,有RWX三种. r(Read,读取):对文件而言,具有读取文件内容的权限:对目录来说,具有浏览目录的权限. w(Write,写入):对文件而言,具有新增,修改,删除文件内 ...

  5. 单元测试框架unitest和自动化测试高级应用

    单元测试框架:为了让单元测试代码更容易维护和编写,遵循一定的规范来编写测试用例. 创建被测类calculator.py   #计算器 class count: def _init_(self,a,b) ...

  6. html, js,css应用文件路径规则

    web前端一般常用文件 .html .css .js.但是当用css文件和html引入资源(比如图片)时,路径可能不相同.下面总结了几条. 使用相对路径引入规则: html或者js引入图片,按照htm ...

  7. Android Ant Build 遇到的问题

    Ant的具体使用这里就不详细说明了,这里记录下自己使用Ant批量打包apk的时候遇到的问题. 1. build 出现 crunch等字样的错误 <span style="color: ...

  8. Django之auth认证

    Auth模块是Django自带的用户认证模块: 我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册.用户登录.用户认证.注销.修改密码等功能,这还真是个麻烦的 ...

  9. 我的GitHub:https://github.com/javaFesh?tab=repositories

    https://github.com/javaFesh?tab=repositories

  10. virt-install创建虚拟机并制作成模板

    一.使用virt-install创建新的虚拟机 virt--template --ram --vcpu= --virt-type kvm --cdrom=/Data/kvm/iso/CentOS-.i ...