TypeScript 学习笔记(二):接口与类型别名、字面量类型

一、接口的定义

在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可以满足实际需要。 typescrip中的接口类似于java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

二、接口的用途

接口的用途就是对行为和动作进行规范和约束,跟抽象类有点像,但是,接口中不能有方法体,只允许有方法定义。

三、接口用法:

1. 使用interface来定义接口:

  interface Info {firstName: string;lastName: string;}const getFullName = ({ firstName, lastName }: Info) => {return `${firstName} ${lastName}`;};console.log(getFullName({ firstName: '123', lastName: '1231' }));

注意在定义接口的时候,你不要把它理解为是在定义一个对象,而要理解为{}括号包裹的是一个代码块,里面是一条条声明语句,只不过声明的不是变量的值而是类型。声明也不用等号赋值,而是冒号指定类型。每条声明之前用换行分隔即可,或者也可以使用分号或者逗号,都是可以的。

2. 可选属性

接口设置可选属性,在属性名后面加个?即可:

interface Vegetables {color?: string;type: string;
}

3. 多余属性检查

getVegetables({type: "tomato",size: "big" // 'size'不在类型'Vegetables'中
});

我们看到,传入的参数没有 color 属性,但也没有错误,因为它是可选属性。但是我们多传入了一个 size 属性,这同样会报错,TypeScript 会告诉你,接口上不存在你多余的这个属性。只要接口中没有定义这个属性,就会报错,但如果你定义了可选属性 size,那么上面的例子就不会报错。

4. 绕开多余属性检查

  • 什么是接口的多余参数检查
interface Baseinfo {name:string,sex?:string
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {console.log(`姓名:${parmasinfo.name }`)
}// 如果直接传递参数,且传递的参数key未在接口中定义会提示错误
printPesonInfo( {name:'wang',age:13} ) // 报错的

在这里插入图片描述

  • 使用类型断言

类型断言就是用来明确告诉 TypeScript,我们已经自行进行了检查,确保这个类型没有问题,希望 TypeScript 对此不进行检查,所以最简单的方式就是使用类型断言:

interface Baseinfo {name:string,sex?:string
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {console.log(`姓名:${parmasinfo.name }`)
}// 利用类型断言,告诉编译器我们传递的参数 就是Baseinfo 接口的东西
printPesonInfo( {name:'wang',age:13} as Baseinfo ) // wang
  • 索引签名
interface Baseinfo {name:string,sex?:string,[other:string]:any
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {console.log(`姓名:${parmasinfo.name }`)
}// 接口中的索引签名other 就会收到age
printPesonInfo( {name:'wang',age:13}) // wang
  • 利用类型兼容性
interface Baseinfo {name:string,sex?:string,
}
// 人 
function printPesonInfo(parmasinfo: Baseinfo) {console.log(`姓名:${parmasinfo.name }`)
}let paramsinfo = {name:'wang',age:13} 
// 类型兼容性就是我们定义的paramsinfo 不管有都少东西,只要包含接口中定义的即可
printPesonInfo(paramsinfo) // 姓名:wang

5. 只读属性

关键字:readonly

const NAME: string = "Lison";
NAME = "Haha"; // Uncaught TypeError: Assignment to constant variableconst obj = {name: "lison"
};
obj.name = "Haha";interface Info {readonly name: string;
}
const info: Info = {name: "Lison"
};
info["name"] = "Haha"; // Cannot assign to 'name' because it is a read-only property

6. 函数类型

接口可以描述普通对象,还可以描述函数类型,我们先看写法:

interface AddFunc {(num1: number, num2: number): number;
}

这里我们定义了一个AddFunc结构,这个结构要求实现这个结构的值,必须包含一个和结构里定义的函数一样参数、一样返回值的方法,或者这个值就是符合这个函数要求的函数。我们管花括号里包着的内容为调用签名,它由带有参数类型的参数列表和返回值类型组成。后面学到类型别名一节时我们还会学习其他写法。来看下如何使用:

const add: AddFunc = (n1, n2) => n1 + n2;
const join: AddFunc = (n1, n2) => ${n1} ${n2}; // 不能将类型'string'分配给类型'number'
add("a", 2); // 类型'string'的参数不能赋给类型'number'的参数

上面我们定义的add函数接收两个数值类型的参数,返回的结果也是数值类型,所以没有问题。而join函数参数类型没错,但是返回的是字符串,所以会报错。而当我们调用add函数时,传入的参数如果和接口定义的类型不一致,也会报错。

你应该注意到了,实际定义函数的时候,名字是无需和接口中参数名相同的,只需要位置对应即可。

四、接口的高阶用法

1. 索引类型

我们可以使用接口描述索引的类型和通过索引得到的值的类型,比如一个数组[‘a’, ‘b’],数字索引0对应的通过索引得到的值为’a’。我们可以同时给索引和值都设置类型,看下面的示例:

  interface RoleDic {[id: number]: string;}const role1: RoleDic = {0: "superadmin",1: "admin"};console.log(role1);const role2: RoleDic = {s: "superadmin",  // error 不能将类型"{ s: string; a: string; }"分配给类型"RoleDic"。a: "admin"};console.log(role2);const role3: RoleDic = ["super_admin", "admin"];console.log(role3);

role2 报错信息:
在这里插入图片描述
上面的例子中 role3 定义了一个数组,索引为数值类型,值为字符串类型。

你也可以给索引设置readonly,从而防止索引返回值被修改。

interface RoleDic {readonly [id: number]: string;
}
const role: RoleDic = {0: "super_admin"
};
role[0] = "admin"; // error 类型"RoleDic"中的索引签名仅允许读取

这里有的点需要注意,你可以设置索引类型为 number。但是这样如果你将属性名设置为字符串类型,则会报错;但是如果你设置索引类型为字符串类型,那么即便你的属性名设置的是数值类型,也没问题。因为 JS 在访问属性值的时候,如果属性名是数值类型,会先将数值类型转为字符串,然后再去访问。你可以看下这个例子:

const obj = {123: "a", // 这里定义一个数值类型的123这个属性"123": "b" // 这里在定义一个字符串类型的123这个属性,这里会报错:标识符“"123"”重复。
};
console.log(obj); // { '123': 'b' }

在这里插入图片描述
如果数值类型的属性名不会转为字符串类型,那么这里数值123和字符串123是不同的两个值,则最后对象obj应该同时有这两个属性;但是实际打印出来的obj只有一个属性,属性名为字符串"123",而且值为"b",说明数值类型属性名123被覆盖掉了,就是因为它被转为了字符串类型属性名"123";又因为一个对象中多个相同属性名的属性,定义在后面的会覆盖前面的,所以结果就是obj只保留了后面定义的属性值。

2. 继承接口

接口可以继承,这和类一样,这提高了接口的可复用性。来看一个场景:

我们定义一个Vegetables接口,它会对color属性进行限制。再定义两个接口,一个为Tomato,一个为Carrot,这两个类都需要对color进行限制,而各自又有各自独有的属性限制,我们可以这样定义:

interface Vegetables {color: string;
}
interface Tomato {color: string;radius: number;
}
interface Carrot {color: string;length: number;
}

三个接口中都有对color的定义,但是这样写很繁琐,所以我们可以用继承来改写:

 interface Vegetables {color: string;
}
interface Tomato extends Vegetables {radius: number;
}
interface Carrot extends Vegetables {length: number;
}
const tomato: Tomato = {radius: 1.2 // error  Property 'color' is missing in type '{ radius: number; }'
};
const carrot: Carrot = {color: "orange",length: 20
};

上面定义的 tomato 变量因为缺少了从Vegetables接口继承来的 color 属性,从而报错。

一个接口可以被多个接口继承,同样,一个接口也可以继承多个接口,多个接口用逗号隔开。比如我们再定义一个Food接口,Tomato 也可以继承 Food:

interface Vegetables {color: string;
}
interface Food {type: string;
}
interface Tomato extends Food, Vegetables {radius: number;
}const tomato: Tomato = {type: "vegetables",color: "red",radius: 1.2
};  // 在定义tomato变量时将继承过来的color和type属性同时声明

3. 混合类型接口

JS 中,函数是对象类型。对象可以有属性,所以有时我们的一个对象,它既是一个函数,也包含一些属性。比如我们要实现一个计数器函数,比较直接的做法是定义一个函数和一个全局变量:

let count = 0;
const countUp = () => count++;

但是这种方法需要在函数外面定义一个变量,更优一点的方法是使用闭包:

// javascript
const countUp = (() => {let count = 0;return () => {return ++count;};
})();
console.log(countUp()); // 1
console.log(countUp()); // 2
// javascript
let countUp = () => {return ++countUp.count;
};
countUp.count = 0;
console.log(countUp()); // 1
console.log(countUp()); // 2

我们可以看到,我们把一个函数赋值给countUp,又给它绑定了一个属性count,我们的计数保存在这个 count 属性中。

我们可以使用混合类型接口来指定上面例子中 countUp 的类型:

interface Counter {(): void; // 这里定义Counter这个结构必须包含一个函数,函数的要求是无参数,返回值为void,即无返回值count: number; // 而且这个结构还必须包含一个名为count、值的类型为number类型的属性
}
const getCounter = (): Counter => { // 这里定义一个函数用来返回这个计数器const c = () => { // 定义一个函数,逻辑和前面例子的一样c.count++;};c.count = 0; // 再给这个函数添加一个count属性初始值为0return c; // 最后返回这个函数对象
};
const counter: Counter = getCounter(); // 通过getCounter函数得到这个计数器
counter();
console.log(counter.count); // 1
counter();
console.log(counter.count); // 2

上面的例子中,getCounter函数返回值类型为Counter,它是一个函数,无返回值,即返回值类型为void,它还包含一个属性count,属性返回值类型为number。

五、类型别名

类型别名就是给一种类型起个别的名字,之后只要使用这个类型的地方,都可以用这个名字作为类型代替,但是它只是起了一个名字,并不是创建了一个新类型。这种感觉就像 JS 中对象的赋值,你可以把一个对象赋给一个变量,使用这个对象的地方都可以用这个变量代替,但你并不是创建了一个新对象,而是通过引用来使用这个对象。

使用 type 关键字,定义类型别名:

type TypeString = string;
let str: TypeString;
str = 123; // error Type '123' is not assignable to type 'string'
  • 类型别名也可以使用泛型:
type PositionType<T> = { x: T; y: T };
const position1: PositionType<number> = {x: 1,y: -1
};
const position2: PositionType<string> = {x: "right",y: "top"
};
  • 使用类型别名时也可以在属性中引用自己:
type Child<T> = {current: T;child?: Child<T>;
};
let ccc: Child<string> = {current: "first",child: {// errorcurrent: "second",child: {current: "third",child: "test" // 这个地方不符合type,造成最外层child处报错}}
};

在这里插入图片描述

  • 但是要注意,只可以在对象属性中引用类型别名自己,不能直接使用,比如下面这样是不对的:
type Child = Child[]; // error 类型别名“Child”循环引用自身

在这里插入图片描述
因为类型别名只是为其它类型起了个新名字来引用这个类型,所以当它为接口起别名时,不能使用 extendsimplements

  • 接口和类型别名有时可以起到同样作用,比如下面这个例子:
type Alias = {num: number;
};
interface Interface {num: number;
}
let alias: Alias = {num: 123
};
let interface: Interface = {num: 321
};
alias = interface;

可以看到用类型别名和接口都可以定义一个只包含 num 属性的对象类型,而且类型是兼容的。那么什么时候用类型别名,什么时候用接口呢?可以通过两点来选择:

  1. 当你定义的类型要用于拓展,即使用 implements 等修饰符时,用接口。
  2. 当无法通过接口,并且需要使用联合类型或元组类型,用类型别名。

六、字面量类型

1. 字符串字面量类型

字符串字面量类型其实就是字符串常量,与字符串类型不同的是它是具体的值。

type Name = "Lison";
const name1: Name = "test"; // error 不能将类型“"test"”分配给类型“"Lison"”
console.log(name1);
const name2: Name = "Lison";
console.log(name2); 

在这里插入图片描述
使用联合类型来使用多个字符串:

type Direction = "north" | "east" | "south" | "west";
function getDirectionFirstLetter(direction: Direction) {return direction.substr(0, 1);
}
getDirectionFirstLetter("test"); // error 类型“"test"”的参数不能赋给类型“Direction”的参数
getDirectionFirstLetter("east");

2. 数字字面量类型

另一个字面量类型就是数字字面量类型,它和字符串字面量类型差不多,都是指定类型为具体的值。

type Age = 18;
interface Info {name: string;age: Age;
}
const info: Info = {name: "Lison",age: 28 // error 不能将类型“28”分配给类型“18”
};

这里补充一个比较经典的逻辑错误,来看例子:

function getValue(index: number) {if (index !== 0 || index !== 1) {// error This condition will always return 'true' since the types '0' and '1' have no overlap// ...}
}

在这里插入图片描述

这个例子中,在判断逻辑处使用了 || 符,当 index !== 0 不成立时,说明 index 就是 0,则不应该再判断 index 是否不等于 1;而如果 index !== 0 成立,那后面的判断也不会再执行;所以这个地方会报错。

七、Interface 与 Type 的区别

1. 区别

  1. 接口可以重复定义的接口类型,它的属性会叠加,类型别名不行
interface Language {id: number
}interface Language {name: string
}let lang: Language = {id: 1, // okname: 'name', // ok
}// 如果使用类型别名
/** ts(2300) 重复的标志 */
type Language = {id: number
}/** ts(2300) 重复的标志 */
type Language = {name: string
}
let lang: Language = {id: 1,name: 'name',
}
  1. type 可以使用联合类型和交集,interface 不能使用联合类型和交集组合
type Pet = Dog | Cat// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
  1. type 支持类型映射,interface不支持
type Keys = "firstname" | "surname"type DudeType = {[key in Keys]: string
}const test: DudeType = {firstname: "Pawel",surname: "Grzybek"
}// 报错
//interface DudeType {
//  [key in keys]: string
//}

2. 相同点

都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface , 虽然效果差不多,但是两者语法不同。

  • interface extends interface
interface Name { name: string; 
}
interface User extends Name { age: number; 
}
  • type extends type
type Name = { name: string; 
}
type User = Name & { age: number  };
  • interface extends type
type Name = { name: string; 
}
interface User extends Name { age: number; 
}
  • type extends interface
interface Name { name: string; 
}
type User = Name & { age: number; 
}

总结:
interface 只能用于定义对象类型和方法,而 type 的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围显然更加广泛。

但是interface也有其特定的用处:
interface 方式可以实现接口的 extends 和 implements
interface 可以实现接口合并声明

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/19985.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL内外连接

目录 内连接 外连接 左外连接 右外连接 内连接 内连接实际上就是利用 where 子句对两种表形成的笛卡儿积进行筛选 select 字段 from 表 1 inner join 表 2 on 连接条件 and 其他条件 显示SMITH的名字和部门名称 使用inner join 写法上是基本类似的。 外连接 左外…

如何使用ChatGPT制作免费的数字人

传统的数字人制作过程 制作属于自己的免费的数字人是一个复杂的过程&#xff0c;需要涉及多个方面的知识和技术。以下是一个大致的步骤指南&#xff0c;以帮助你开始这个过程&#xff1a; 1. 确定数字人的目标和设计&#xff1a;首先&#xff0c;你需要确定数字人的用途和目标…

校园跑腿小程序都包括哪些业务呢?

校园创业&#xff0c;大家都知道&#xff0c;一直以来是个非常火热的话题&#xff0c;每位步入大学校园的学子们都有过在校创业的想法&#xff0c;包括我们一些有着学校资源的社会创业者们&#xff0c;早已经看好了中国高校这块巨大的市场。 在这里&#xff0c;我要跟大家分享…

kubernetes 1.27.3 集群部署方案

一、准备环境 1.1 Kubernetes 1.27.3 版本集群部署环境准备 1.1.1 主机硬件配置说明 cpu内存硬盘角色主机名系统版本 8C 8G 1024GB master master01 centos 7.9 8C 16G 1024GB worker(node) worker01 centos 7.9 8C 16G 1024GB worker(node) worker…

3. MySQL - 数据类型 选项约束

目录 回顾 1. 命令行下的 MySql 客户端 2. 图形化界面的 MySQL-Client 3. 数据库概述 3.1 数据库管理系统是什么 3.2 工作模式 3.3 RDBMS 管理数据的结构 3.4 客户端连接服务器的信息 4. MySQL 中的数据类型 4.1 整型类型 4.2 字符串 4.3 日期/时间 5. MySQL 每个字…

阿里云容蓓:DCDN 助力云原生时代的应用构建及最佳实践

在数字化转型速度不断提升的今天&#xff0c;大带宽、低时延、高并发的场景不断涌现&#xff0c;内容分发网络(Content Delivery Network&#xff0c;CDN)应用需求还在不断攀升&#xff0c;打造更高质量的CDN服务将成为新时代产业竞争的关键所在。亚太CDN峰会自2012年第一届以来…

mac安装Golang开发环境及入门

目录 一、Mac brew 安装go环境 1.1 安装步骤 1.2 设置GOPATH 及环境变量 1.3 编写第一个go程序 二、快速入门 1.1 快速入门需求 1.2 go学习&#xff08;自用&#xff09; 一、Mac brew 安装go环境 1.1 安装步骤 1&#xff09;终端输入&#xff0c;也可以指定下载go版本…

Hadoop 3.2.4 本机伪分布式安装

Hadoop 3.2.4 伪分布式安装 文章目录 Hadoop 3.2.4 伪分布式安装前言配置ssh免密登录下载安装包解压并调整配置文件解压安装包到当前位置调整配置文件hadoop-env.shyarn-env.shcore-site.xmlhdfs-site.xmlmapred-site.xmlyarn-site.xmlworkers 配置 启动验证启动与命令查验web页…

高并发的哲学原理(一)-- 找出单点,进行拆分

人列计算机 《三体》中&#xff0c;刘慈欣设计了一个用人进行二进制运算的计算机&#xff0c;使用了三千万名士兵(晶体管)&#xff1a; 计算机名&#xff1a;秦一号 CPU&#xff1a;秦始皇最精锐的五个军团 挥舞旗帜进行二进制运算 用三个士兵来组成与门、或门、与非门、或非门…

Python GUI编程利器:Tkinker中的消息对话框(13)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 今日目标 学习Tkinter中的消息对话框的使用&#xff0c;实现如下效果&#xff1a; 文本消息对话框 可以通过showinfo()创建文…

【网络安全带你练爬虫-100练】第12练:pyquery解析库提取指定数据

目录 一、目标1、基础/环境的准备工作 二、目标2&#xff1a;开始使用pyquery 三、目标3&#xff1a;提取到指定的数据 四、目标3&#xff1a;通过列表的形式获取指定数据 五、扩展&#xff1a;其他方法 六、网络安全O 一、目标1、基础/环境的准备工作 1、文档&#xff1…

【原生HTML+SpringBoot】电子病历编辑器源码

一、简介 本系统主要面向医院医生、护士&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。本系统基于云端SaaS服务方式&#xff0c;通过浏览器方式访问和使用系统功能&#xff0c;提供电子病历在线制作、管理和使用的一体化电子病历解决方案&#x…