TS - Interfaces详解

一、什么是接口

One of the core principles of typescript is to type check the shape the value has. It is sometimes called "duck type discrimination" or "structural subtype". In typescript, the function of an interface is to name these types and define contracts for your code or third-party code.

  • 常用于对对象的形状(Shape)进行描述。
  • 可用于对类的一部分行为进行抽象
  • 为类型命名和为你的代码或第三方代码定义契约。

二、对象与接口

2.1 对象接口简例

接口一般首字母大写。tslint建议加上I前缀, 否则会发出警告,可在tslint中关闭它,在rules添加规则 ["interface-name":[true,"nerver-prefix"]

interface Person {
  name: string,
  age: number
}
const why: Person = {
  name: 'why',
  age: 18
}

首先我们定义了一个接口Person,接口约束了shape必须要有name,age两个属性,并且有对应的数据类型,然后我们创建了 why对象面向Person


定义的变量比接口少一些属性是不被允许的:
IDE 添加了tslint插件会发出警告,编译和浏览器控制台会报错

interface Person {
  name: string,
  age: number
}
const why: Person = {
  name: 'why',
}
// Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.

属性溢出也是不允许的:

interface Person {
  name: string,
  age: number
}
const why: Person = {
  name: 'why',
  age: 18,
  gender: 'female'
}

需要注意接口不能转换为 JavaScript。 它只是 TypeScript 的一部分。
可在TypeScript学习乐园实时转换TypeScript

2.2 可选属性与只读属性

如果希望不要完全匹配形状,那么可以用可选属性:?
如果希望对象中的一些字段只读,那么可以用 readonly 定义

interface Person {
  name?: string,
  readonly age: number,
  run?(): void,
}
const why: Person = {
  // name 键值对缺失不会报错
  age: 18
}
why.run&&why.run() //判断函数执行
why.age++  //修改age报错 Cannot assign to 'age' because it is a read-only property.

2.3 任意属性

如果希望一个接口有任意的属性,可以使用如下方式
(1) 可索引类型

interface Person {
  name: string,
  age?: number,
  [attr: string]: any
}
const why: Person = {
  name: 'why',
  age: 18,
  gender:'female'
}

需要注意,定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,否则会出现unexpected error.

interface Person {
  name: string,
  age?: number,
  [attr: string]: string // error
}
const why: Person = {
  name: 'why',
  age: 18,
  gender:'female'

  /* Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
   Property 'age' is incompatible with index signature.
   Type 'number' is not assignable to type 'string'. */
}

(2) 类型断言

有两种方法

interface Person {
  name: string,
  age?: number,
}
const why: Person = <Person>{
  name: 'why',
  age: 18,
  gender:'female'
} 

当然要配置 tslint "no-angle-bracket-type-assertion":false否则tslint会报错,因为它推荐你使用第二种方式

interface Person {
  name: string,
  age?: number,
}
const why: Person = {
  name: 'why',
  age: 18,
  gender:'female'
} as Person

(3) 类型兼容性

interface IUser {
  name: string,
  age: number,
}
type IUserFunc = (user: IUser) => string

const foo: IUserFunc = (user: IUser) => {
  return user.name
}

foo({ name: 'clc', age: 18, gender: 'male' }) //error
// TS2345: Argument of type '{ name: string; age: number; gender: string; }' is not assignable to parameter of type 'IUser'.
//Object literal may only specify known properties, and 'gender' does not exist in type 'IUser'.

const bar = { name: 'clc', age: 18, gender: 'male' }
foo(bar)
// 用变量bar 接收参数再传入正常编译

直接传参报错,但用变量bar接收再传入正常编译

interface IUser {
  name: string,
  age: number,
}

const user: IUser = {} // missing the following properties from type 'IUser': name, age

const user = {} as IUser // right

2.4 联合类型

interface UnionType {
  bar: any[] | (() => void) | object
}
const foo:UnionType = {
  bar:['w','h','y']
// bar 是一个any类型的数组
}
const foo:UnionType = {
  bar() {}
// bar 是一个返回值为void的函数
}
const foo:UnionType = {
  bar: {}
// bar 是一个对象
}

2.5 可索引类型

可索引类型内有一个索引签名,它表示了索引的类型,还有索引值对应的值的类型

2.5.1 数字索引签名

index 作为签名占位,可为任意字符.

interface NumMap {
  [index: number]: string
}
const obj: NumMap = {
  0: 'foo',
  1: 'bar',
  '2': 'why',
  'c': 'clc' //errorType  is not assignable to type 'NumMap'.Object literal may only specify known properties, and ''c'' does not exist in type 'NumMap'.
}
obj[0] // foo
obj[2] // why

数字作为对象的键是转换为字符形式的,key为数字字符串正常编译,若为非数字字符串,报错.


数组本身索引签名即为number 类型

interface NumMap {
  [index: number]: string
}
let arr: NumMap
arr = ['foo', 'bar']
2.5.2 字符索引签名
interface StrMap {
  [str: string]: string
}
let obj: StrMap = {
  foo: 'foo',
  bar: 'bar',
  1: 'why'  // 正常编译
}
const arr: StrMap = ['foo','bar'] // error 缺少字符索引签名

字符索引签名类型包含一部分数字索引签名类型


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

interface StrNumMap {
  [str: string]: any,  // any
  [index:number]: string
}
let obj: StrNumMap = {
  foo: 'foo',
  bar: 'bar',
  1: 'why'
}
const arr: StrNumMap = ['foo','bar'] // 正常编译

2.6 可同时面向多个接口

const user: IUser | IStudent = {}

三、函数与接口

3.1 TypeScript函数存在的问题

为了防止错误调用,给函数的参数和返回值定义了类型限制,但代码长不方便阅读

const foo = (name: string, age: number): string => {
  return `name : ${name}, age: ${age}`
}
3.1.1 使用接口重构一
interface IUser {
  name: string,
  age: number,
}

const foo = ({name,age}:IUser): string => {
  return `name : ${name}, age: ${age}`
}

还可以把参数对象替换成一个变量user

interface IUser {
  name: string,
  age: number,
}

const foo = (user:IUser): string => {
  return `name : ${user.name}, age: ${user.age}`
}
3.1.2 使用接口重构二

定义函数还可以写成如下形式

const foo: (name: string, age: number) => string = (name, age) => {
  return `name : ${name}, age: ${age}`
}
// 给一个变量foo定义了一个函数类型,并且把一个函数赋值给了foo

接着定义两个接口

interface IUser {
  name: string,
  age: number,
}
interface IUserFunc {
  (user:IUser):string
}
// warn Interface has only a call signature — use `type IUserFunc = // (user:IUser) =>string` instead.

第二个接口会报警告,tslint建议您如果一个函数接口只有一个方法,建议使用类型别名,type语句, 可配置["callable-types":false] 关掉它,或者听它的吧

type IUserFunc = (user: IUser) => string

接下来按步重构函数

interface IUser {
  name: string,
  age: number,
}
type IUserFunc = (user: IUser) => string

const foo: (name: string, age: number) => string = (name, age) => {
  return `name : ${name}, age: ${age}`
}

面向第一个接口

const foo: (user:IUser) => string = (user) => {
  return `name : ${user.name}, age: ${user.age}`
}

next

const foo: IUserFunc = user => {
  return `name : ${user.name}, age: ${user.age}`
}

函数简短了许多,并且看接口就可以知道函数参数,返回值的类型限制

3.2 混合类型

interface Counter {
  count: number,
  ():void
}
function getFunc(): Counter {
  function fn(){
    fn.count++
  }
  fn.count = 0
  return fn
}

函数getFunc返回类型为混合类型,即做为函数和对象使用,并带有额外的属性。

四、类与接口

接口对类的一部分行为进行抽象。

4.1 类实现接口

实现(implements)是面向对象中的一个重要概念。一个类只能继承自另一个类,不同类之间可以有一些共有的特性,就可以把特性提取成接口(interfaces),用 implements 关键字来实现。


(1)TypeScript中定义一个基本的类

class Person {
  name: string
  run:Function
  constructor(name: string) {
    this.name = name
    this.run=()=>{}
  }
}
class Rapper extends Person {
  age: number
  constructor(name: string, age: number) {
    super(name)
    this.age = age
  }
  rap(){
    console.log('I can sing and dance tap');
  }
}

(2) implements 关键字实现接口

interface Skr {
  name: string,
  age: number,
  rap():void
}
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}
class Rapper extends Person implements Skr{
  age: number
  constructor(name: string, age: number) {
    super(name)
    this.age = age
  }
  rap(){
    console.log('I can sing and dance tap');
  }
}

虽然定义了一个接口,但是在接口类中的属性和方法还是要写类型限制.感觉代码反而应为接口冗余了.
但类的接口是一种规范,因为接口分离了规范和实现,可通过接口轻易解读类必须提供的属性和方法,增强了可拓展性和可维护性.


(3) 一个类可实现多个接口

interface Skr {
  name: string,
  age: number,
  rap():void
}
interface SkrSkr{
  sing():void
}
class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}
class Rapper extends Person implements Skr,SkrSkr{
  age: number
  constructor(name: string, age: number) {
    super(name)
    this.age = age
  }
  rap(){
    console.log('I can sing and dance tap');
  }
  sing(){
    console.log('I can sing and dance tap');
  }
}

五、接口继承

  • 接口可以通过其他接口来扩展自己。
  • Typescript 允许接口继承多个接口。
  • 继承使用关键字 extends。

5.1 单接口继承

extends 关键字后加继承的接口

interface Person {
  name: string,
  age: number
}
interface Students extends Person {
  gender: string
}
const foo: Students = {
  name: 'why',
  age: 18,
  gender: 'female'
}

5.2 多接口继承

多接口之间逗号分隔

interface Sing {
  sing(): void
}
interface Jump {
  jump(): void
}
interface Rap extends Sing, Jump {
  rap(): void
}
const foo: Rap = {
  sing(){},
  jump(){},
  rap(){}
}

5.3 接口继承类

常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可行的
用extends关键字继承类

class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  run(): void { }
}
interface User extends Person {
  gender: string
}
const foo: User = {
  name: 'foo',
  age: 18,
  gender: 'male',
  run():void { }
}

本质上就是接口继承接口

interface Person {
  name: string,
  age: number,
  run(): void
}
interface User extends Person {
  gender: string
}
const foo: User = {
  name: 'foo',
  age: 18,
  gender: 'male',
  run(): void { }
}

因为声明 class Person时,除了创建一个名为 Person的类,同时也创建了一个名为 Person的类型(实例的类型)。
所以 Person既可以 当做一个类来用,也可以把它实例的类型当成接口使用

class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  run(): void { }
}
const foo: Person = {
  name:'foo',
  age:18,
  run(){}
}

实例的类型是不包括构造函数constructor、静态属性或静态方法,因为生成的实例也不包含这些

class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
  run(): void { }
  static age: number
  static jump(): void { }
}
const foo: Person = {
  name: 'foo',
  age: 18, //  “age”不在类型“Person”中
  run() { },
  jump(): void { } // jump”不在类型“Person”中
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容