TypeScript 真香系列——接口篇

接口带来了什么好处

好处One —— 过去我们写 JavaScript

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

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

函数调用:

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

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

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

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

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

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

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

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

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

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

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

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

我们先定义一个 IUser 接口:

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

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

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

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

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

定义两个接口:

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

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

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

// 正确的调用

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

// 错误的调用

getUserInfo();

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

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

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

const goodParams: IGoodsBody = this.ctx.body;

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

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

接口的基础篇

接口的定义

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

1interface IQuery { 2 page: number; 3}

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

接口中定义方法

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

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

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

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

接口中定义属性

普通属性

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

可选属性

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

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

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

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

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

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

正确的调用方式如下:

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

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

只读属性

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

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

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

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

接口的高级篇

函数类型接口

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

  • 定义了一个函数接口

  • 接口接收三个参数并且不返回任何值

  • 使用函数表达式来定义这种形状的函数

    interface Func { // ✔️ 定于这个函数接收两个必选参数都是 number 类型,以及一个可选的字符串参数 desc,这个函数不返回任何值 (x: number, y: number, desc?: string): void }

    const sum: Func = function (x, y, desc = '') { // const sum: Func = function (x: number, y: number, desc: string): void { // ts类型系统默认推论可以不必书写上述类型定义 console.log(desc, x + y) }

    sum(32, 22)

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

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

接口的实现

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

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

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

// 实现这样一个接口

1class 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}

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

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

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

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

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

接口的继承

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

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

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

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

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

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

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

可索引类型接口

interface和type的区别

type 可以而 interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型

    // 基本类型别名 type Name = string

    // 联合类型 interface Dog { wong(); } interface Cat { miao(); }

    type Pet = Dog | Cat

    // 具体定义数组每个位置的类型 type PetList = [Dog, Pet]

  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值

    // 当你想获取一个变量的类型时,使用 typeof

    let div = document.createElement('div'); type B = typeof div

  • type 其他骚操作

    type StringOrNumber = string | number;
    type Text = string | { text: string };
    type NameLookup = Dictionary<string, Person>;
    type Callback<T> = (data: T) => void;
    type Pair<T> = [T, T];
    type Coordinates = Pair<number>;
    type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

interface 可以而 type 不行

interface 能够声明合并

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

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

接口的应用场景总结

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

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

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

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )