在前面我们通过type可以用来声明一个对象类型:
通过类型别名(type), 声明一个对象类型
type InfoType = {
name: string
age: number
}
const info: InfoType = {
name: "chnyq",
age: 18
}
对象类型的另外一种声明方式, 就是通过接口来声明:
通过接口, 声明一个对象类型
interface InfoType {
name: string
age: number
}
const info: InfoType = {
name: "chnyq",
age: 18
}
使用接口定义时, 有一个规范, 接口名前面加上一个"I", 这个是规范, 遵不遵守看个人意愿
// 接口名前面加上一个大写的I
interface IInfoType {
name: string
age: number
}
const info: IInfoType = {
name: "chnyq",
age: 18
}
接口定义对象类型时, 同样可以定义可选类型, 使用方式和之前是一样的
interface InfoType {
name: string
// 添加可选类型
age?: number
}
const info: InfoType = {
name: "chnyq",
// age: 18
}
接口也可以定义只读属性, readonly
interface InfoType {
// 定义只读属性
readonly name: string
age: number
}
const info: InfoType = {
name: "chnyq",
age: 18
}
两种定义对象类型的方式在使用上的区别,我会在下面讲到, 接下来我们继续学习一下接口的其他特性。
前面我们使用interface来定义对象类型,这个时候其中的属性名、类型、方法都是确定的,但是有时候我们会遇 到类似下面的对象:
例如我想使用一个对象(正常用数组, 在这里我偏要用对象o.0), 来存放我们前端需要学习的部分技术
const frontLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue",
4: "TypeScript"
}
此时我们对象中key是number类型, value是string类型
如果我们想要限制对象后续增加的属性, key也是number类型, value也是string类型的话, 我们可以使用接口定义索引类型进行限制
interface IndexLanguage {
// 表示索引key为number类型, value为string类型
// index相当于是形参, 可以自己取名的
[index: number]: string
}
const frontLanguage: IndexLanguage = {
0: "HTML",
1: "CSS",
2: "JavaScript",
3: "Vue",
4: "TypeScript",
// 再添加其他类型的话就会报错
// "abc": "12"
}
当然, key不是只能为number类型, value也不是只能为string类型, 这个都是可以自定义的
interface LanguageBirth {
[name: string]: number
}
const language: LanguageBirth = {
"Java": 1995,
"JavaScript": 1996,
"C": 1972
}
前面我们都是通过interface来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型:
// 接口定义函数类型
interface CalcFun {
// 固定的语法
(n1: number, n2: number): number
}
const add: CalcFun = (n1: number, n2: number) => {
return n1 + n2
}
const mul: CalcFun = (num1: number, num2: number) => {
return num1 * num2
}
// 测试
console.log(add(10, 20)) // 30
console.log(mul(10, 20)) // 200
当然,除非特别的情况,函数类型还是推荐大家使用类型别名的方式来定义:
type CalcFun = (n1: number, n2: number) => number
接口和类一样是可以进行继承的,也是使用extends关键字:
interface Swim {
swimming: () => void
}
// Action接口继承自Swim接口
interface Action extends Swim {}
// 由于Action继承自Swim, 意味着使用Action类型, 同样需要对swimming方法实现
const action: Action = {
swimming() {
console.log("swimming")
}
}
并且我们会发现,接口是支持多继承的(类不支持多继承)
例如下面代码, 我定义两个接口: Swim和Fly, 我们使用一个接口继承自这两个接口
interface Swim {
swimming: () => void
}
interface Fly {
flying: () => void
}
// Action接口继承自Swim和Fly接口
interface Action extends Swim, Fly {}
// 意味着使用Action类型, 需要对swimming和flying方法都进行实现
const action: Action = {
swimming() {
console.log("swimming")
},
flying() {
console.log("flying")
}
}
前面我们学习了联合类型:
联合类型表示多个类型中一个即可
type IDType = number | string
还有另外一种类型合并方式,就是交叉类型( Intersection Types):
交叉类似表示需要满足多个类型的条件;
交叉类型使用 & 符号;
我们来看下面的交叉类型:
表达的含义是number和string要同时满足;
但是有同时满足是一个number又是一个string的值吗?其实是没有的,所以MyType其实是一个never类型;
type IDType = number & string
交叉类型的应用
在开发中,我们使用交叉类型时,通常是对对象类型进行交叉的
interface Swim {
swimming: () => void
}
interface Fly {
flying: () => void
}
// 定义一个交叉类型
type myType = Swim & Fly
// 使用交叉类型, 需要同时实现Swim和Fly类型的方法
const obj: myType = {
swimming() {
console.log("swimming")
},
flying() {
console.log("flying")
}
}
也就是说, 我们组合接口就有了两种方式
方式一: 使用接口的多继承
方式二: 交叉类型来结合
接口定义后不仅可以作为类型,接口也是可以被类实现的:
实现接口是使用的
implements关键字
interface Swim {
swimming: () => void
}
// 类实现接口
class Action implements Swim {
swimming() {
console.log("swimming")
}
}
类实现接口也是可以进行多实现的
interface Swim {
swimming: () => void
}
interface Fly {
flying: () => void
}
// 类多实现接口
class Action implements Swim, Fly {
swimming() {
console.log("swimming")
}
flying() {
console.log("flying")
}
}
如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入;
这就是面向接口开发;
// 函数要求传入一个接口
function swim(swimmer: Swim) {
swimmer.swimming()
}
const act = new Action()
// 由于Action类实现了接口, 所以类可以直接传入
swim(act)
一个类, 是可以同时继承父类, 又实现或者多实现接口
interface Swim {
swimming: () => void
}
interface Fly {
flying: () => void
}
class Animal {
}
// 继承父类的同时, 又实现接口
class Action extends Animal implements Swim, Fly {
swimming() {
console.log("swimming")
}
flying() {
console.log("flying")
}
}
我们会发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
如果是定义非对象类型,通常推荐使用type,比如Direction、 Alignment、一些Function;
如果是定义对象类型,那么他们是有区别的:
对于名称相同的接口, 会将属性进行合并
interface Foo {
name: string
}
interface Foo {
age: number
}
// name和age都需要定义
const foo: Foo = {
name: "chenyq",
age: 18
}
而type定义的是别名,别名是不能重复的;
对于定义对象类型, 建议使用interface(官方给的建议也是interface), 也可以根据自己的爱好选择