# interface接口和type类型[接口与类型别名]

# 对象类型定义

function printCoord(pt: { x: number
   y: number }) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });

pt的类型注解:可省略(换行),可分号,可逗号

  • 对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?:
// Point: x/y/z -> 对象类型
// {x: number, y: number, z?: number}
function printPoint(point: {x: number, y: number, z?: number}) {
  console.log(point.x)
  console.log(point.y)
  console.log(point.z)
}

printPoint({x: 123, y: 321})
printPoint({x: 123, y: 321, z: 111})

export {}

# ts接口

接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。,在面向对象的语言中,interface经常被用来定义一个不包含数据和逻辑代码但是用来签名定义了行为的抽象类型。方便的定义对象的类型,对对象的形状进行描述,对类进行抽象

类型别名有的地方和接口甚至可以通用,但是他们之间还是有区别的,比如,interface只能为定义对象类型(interface A{}),而type可以定义基本类型(注意写法,type A =xxx);如果能用接口表示类型的尽量用接口进行表示。

提示

  • 定义接口要 首字母一般大写。
  • 只需要关注值的 外形,并不像其他语言一样,定义接口是为了实现。
  • 如果没有特殊声明,定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的,赋值的时候,变量的形状必须和接口的形状保持一致。
  • 如果是类接口类型,实现该接口还可以以此扩充

应用场景:在声明一个对象、函数或者类时,先定义接口,确保其数据结构的一致性。 TypeScript 接口定义如下:

interface interface_name { 
}
type Person1 = string

interface里的内容是用分号隔开(可省略),也可以使用逗号,不过一般使用分号,而且某些lint规则会修改成分号。

interface example {
	name: string
	age: number
}
// 接口定义的函数
interface exampleFunc {
	(name:string,age:number): void
}


type example = {
	name: string
	age: number
}
// 类型定义的函数
type example = (name:string,age:number) => void

# ts 接口属性

  • 可选属性

?:表示非必须键;任意键没有明确给字段的key,可以非必须存在

// 语法
interface Clothes {
  color?: string;
  size: string;
  price: number;
}

// 这里可以不定义属性 color
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

  • 只读属性:一些对象属性只能在对象刚刚创建的时候修改其值。你可以在属性名前用 readonly 来指定只读属性,比如价格是不能被修改的
// 语法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
}

// 创建的时候给 price 赋值
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98 
}

// 不可修改
myClothes.price = 100
// error TS2540: Cannot assign to 'price' because it is a constant or a read-only property

  • 任意属性:有时候我们希望接口允许有任意的属性,语法是用 [] 将属性包裹起来:
// 语法
interface Clothes {
  color?: string;
  size: string;
  readonly price: number;
  [propName: string]: any;
  //这里的接口 Clothes 可以有任意数量的属性,并且只要它们不是 color size 和 price,那么就无所谓它们的类型是什么。
}

// 任意属性 activity
let myClothes: Clothes = { 
  size: 'XL', 
  price: 98,
  activity: 'coupon'
}

# 接口定义对象

接口和使用接口的对象必须是高度吻合,不能多或者少,除非是使用了非必须和任意键这两种情况.

interface Person {
  // readonly name: string; //标注readonly只读不可修改
  name: string;
  age?: number;
  [propName: string]: any; //这是个任意键标识
  say(): string;//(method) Person.say(): string
  ko:()=>string//(property) Person.ko: () => string
}

//必须要有name和say,ko,可以不需要age和未明确表明字段的key
let person:Person={
  name:"k1",
  say(){
    return "1"
  },
  ko(){
    return "1221"
  }
}

console.log(person)

转化为js

var person = {
    name: "k1",
    say: function () {
        return "1";
    },
    ko: function () {
        return "1221";
    }
};
console.log(person);

但是要注意的是,以下代码中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。

interface Person {
    name: string;
    age?: number; //会报错
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

# 接口定义数组

interface ColumnProps{
    id:number;
    title:string;
    avatar:string;
    description:string
}

const testData: ColumnProps[] = [{
    id: 1,
    title: "xxx",
    description: '描述',
    avatar: 'xxx'
  }]

  console.log(testData)
interface namelist { 
  [index:number]:string | number
} 

var list2:namelist = ["John",1,"Bran"] 

# ts接口定义函数类型

函数类型使用接口:为了使接口表示函数类型,需要给接口定义一个调用签名。 它就像是一个只有 参数列表 和 返回值类型 的函数定义。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean {
  return source.search(subString) > -1;
}

如果接口中的函数类型带有函数名,下面两种书写方式是等价的

interface Calculate {
  add(x: number, y: number): number
  multiply: (x: number, y: number) => number
}
interface Calculate {
  add(x: number, y: number): number
  multiply: (x: number, y: number) => number
}


let mySearch: Calculate ={
  add:(x:number,y:number):number=>{
    return x+y
  },
  multiply:(x:number,y:number):number=>{
    return x+y
  },
}

# ts接口定义可索引类型

interface ScenicInterface {
  [abc: number]: string
}

let arr: ScenicInterface = ['西湖', '华山', '故宫']
let favorite: string = arr[1]
console.log(favorite)//华山

示例中索引签名是 number类型,返回值是字符串类型。

另外还有一种索引签名是 字符串类型。可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。

interface Foo1 {
  [index: string]: number;
  x: number;
  y: number;
}

// // 错误 Property 'y' of type 'string' is not assignable to 'string' index type 'number'.
// interface Bar {
//   [index: string]: number;
//   x: number;
//   y: string; // Error: y 属性必须为 number 类型
// }


let t22foo:Foo1={
  k: 1,
  x: 2,
  y: 3
}
console.log(t22foo)//{ k: 1, x: 2, y: 3 }

# 接口继承接口

接口继承接口,使用extends关键字

interface Person {
  // readonly name: string;
  name: string;
  age?: number;
  [propName: string]: any;
  say(): string;
}

interface Teacher extends Person {
  teach(): string;
}
interface Radio{
    switchRadio(trigger:boolean):void
}
interface Battery{
    checkBatteryStatus():void
}
interface RadioWithBattery extends Radio{
    checkBatteryStatus():void

}
class Car implements Radio{
    switchRadio(trigger:boolean){

    }
}
class CellPhone implements RadioWithBattery{
    switchRadio(trigger:boolean){

    }
    checkBatteryStatus(){
        
    }
}

接口继承就是说接口可以通过其他接口来扩展自己。Typescript 允许接口继承多个接口。继承使用关键字 extends。TypeScript 编译器依赖接口用于类型检查,最终编译为 JavaScript 后,接口将会被移除。

单接口继承语法格式:

Child_interface_name extends super_interface_name

多接口继承语法格式:

Child_interface_name extends super_interface1_name, super_interface2_name,,super_interfaceN_name
interface Person { 
  age:number 
} 

interface Musician extends Person { 
  instrument:string 
} 

var drummer = <Musician>{}; //也可以这样 {} as Musician
drummer.age = 27 
drummer.instrument = "Drums" 
console.log("年龄:  "+drummer.age)
console.log("喜欢的乐器:  "+drummer.instrument)


var d1:Musician={
  age:30,
  instrument:'go'
}
console.log(d1) // { age: 30, instrument: 'go' }
interface IParent1 { 
  v1:number 
} 

interface IParent2 { 
  v2:number 
} 

interface Child extends IParent1, IParent2 { } 
var Iobj:Child = { v1:12, v2:23} 
console.log("value 1: "+Iobj.v1+" value 2: "+Iobj.v2)// value 1: 12 value 2: 23

# 交叉类型和接口继承实现合并多个功能


interface ISwim {
  swimming: () => void
}

interface IFly {
  flying: () => void
}

type MyType1 = ISwim | IFly
type MyType2 = ISwim & IFly

const obj1: MyType1 = {
  flying() {
  }
}
// 必须两个接口内容都得实现
const obj2: MyType2 = {
  swimming() {
  },
  flying() {
  }
}


interface IAction extends ISwim, IFly {

}

const action: IAction = {
  swimming() {
  },
  flying() {
  }
}

console.log(obj1,obj2,action)

# 类应用接口

类应用接口使用implements关键字

class User implements Person {
  name = 'dell';
  say() {
    return 'hello';
  }
}

# 面向接口编程

interface ISwim {
  swimming: () => void
}

interface IEat {
  eating: () => void
}

// 类实现接口
class Animal {
  
}

// 继承: 只能实现单继承
// 实现: 实现接口, 类可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
  swimming() {
    console.log("Fish Swmming")
  }

  eating() {
    console.log("Fish Eating")
  }
}


class Person implements ISwim {
  swimming() {
    console.log("Person Swimming")
  }
}


// 编写一些公共的API: 面向接口编程
function swimAction(swimable: ISwim) {
  swimable.swimming()
}

// 1.所有实现了接口的类对应的对象, 都是可以传入
swimAction(new Fish())
swimAction(new Person())


swimAction({swimming: function() {}})


# 混合接口应用

接口可以描述函数、对象的方法或者对象的属性。有时希望一个对象同时具有上面提到多种类型,比如一个对象可以当做函数使用,同时又具有属性和方法。

/*
声明一个接口,如果只有 (start: number): string 一个成员,那么这个接口就是函数接口,
同时还具有其他两个成员,可以用来描述对象的属性和方法,这样就构成了一个混合接口。
*/
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

/*创建一个 getCounter() 函数,它的返回值是 Counter 类型的*/
function getCounter(): Counter {
  let counter = function (start: number) { } as Counter;
  counter.interval = 123;
  counter.reset = function () { };
  return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
console.log(c)//[Function: counter] { interval: 5, reset: [Function (anonymous)] }

通过类型断言,将函数对象转换为 Counter 类型,转换后的对象不但实现了函数接口的描述,使之成为一个函数,还具有 interval 属性和 reset() 方法。 断言成功的条件是,两个数据类型只要有一方可以赋值给另一方 ,这里函数类型数据不能赋值给接口类型的变量,因为它不具有 interval 属性和 reset() 方法。

# ts强校验

在ts中,如果如下getPersonName直接传递的是person的内容,则会强校验,里面的内容必须和Person接口吻合且不多余才会通过,而如果使用便利person的名字作为参数,则符合Person接口即可,有多余内容也无关紧要

const getPersonName = (person: Person): void => {
  console.log(person.name);
};
const person = {
  name: 'dell',
  sex: 'male',
  say() {
    return 'say hello';
  },
  teach() {
    return 'teach';
  }
};

getPersonName(person);

# 泛型继承接口

interface Tlength{
    length:number
}

function fun <T extends Tlength> (arg:T):T{
    console.log(arg.length)
    return arg
}

let a = fun("1")
let b = fun({length:1})

# type 和 interface一些应用

类型别名定义

  • 两者最关键的差别在于类型别名本身无法添加新的属性,而接口是可以扩展的。(当然如果通过&这种手法也可以扩展↓↓↓↓↓↓↓)
  • 它俩也支持继承,并且不是独立的,而是可以 互相 继承,只是具体的形式稍有差别.可以看到对于interface来说,继承是通过 extends 实现的,而type的话是通过 & 来实现的,也可以叫做 交叉类型
type exampleType1 = {
	name: string
}
interface exampleInterface1 {
	name: string
}

type exampleType2 = exampleType1 & {
	age: number
}
type exampleType3 = exampleInterface1 & {
	age: number
}
interface exampleInterface2 extends exampleType1 {
	age: number
}
interface exampleInterface3 extends exampleInterface1 {
	age: number
}


let t1:exampleType1 = {
	name:'t1'
}

console.log(t1)//{ name: 't1' }

let i1:exampleInterface1 = {
	name:'i1'
}
console.log(i1)//{ name: 'i1' }

let t2:exampleType2 = {
	name:'t2',
	age:28
}

console.log('t2是type结合交叉类型生成',t2)//t2是type结合交叉类型生成 { name: 't2', age: 28 }
 
let t3:exampleType3 ={
	name:'t3',
	age:28
}

console.log('t3是interface结合交叉类型生成',t3)//t3是interface结合交叉类型生成 { name: 't3', age: 28 }

let i2:exampleInterface2 ={
	name:'i2',
	age:27
}
console.log('i2是interface继承type生成',i2)//i2是interface继承type生成 { name: 'i2', age: 27 }


let i3:exampleInterface3 ={
	name:'i3',
	age:27
}
console.log('i3是interface继承type生成',i3)//i3是interface继承type生成 { name: 'i3', age: 27 }

# type和interface的不同点

type可以做到,但interface不能做到的事情:

  • type可以定义 基本类型的别名,如 type myString = string
  • type可以通过 typeof 操作符来定义,如 type myType = typeof someObj
  • type可以申明 联合类型,如 type unionType = myType1 | myType2
  • type可以申明 元组类型,如 type yuanzu = [myType1, myType2]

通过typeof操作符获取sem变量的类型并赋值给Sem类型变量,之后就可以使用Sem类型

interface Person {
    name: string;
    age: number;
}

const sem: Person = { name: "semlinker", age: 30}

type Sem = typeof sem; // type Sem = Person


const lolo: Sem = { name: "lolo", age: 5 }

console.log(lolo)//{ name: "lolo", age: 5 }

interface可以做到,但是type不可以做到的事情:interface可以声明合并,示例如下

    interface test {
        name: string
    }
    interface test {
        age: number
    }
    
    /*
        test实际为 {
            name: string
            age: number
        }
    */
  • 迭代器
interface IteratorInterface {
  next: () => {
    value: any
    done: boolean
  }
}

function createIterator(array: any[]): IteratorInterface {
  let index = 0
  let len = array.length

  return {
    next: function () {
      return index < len ? { value: array[index++], done: false } : { value: undefined, done: true }
    }
  }
}

var iterator = createIterator([1, 2, 3])

console.log(iterator.next()) // { value: 1, done: false }
console.log(iterator.next()) // { value: 2, done: false }
console.log(iterator.next()) // { value: 3, done: false }
console.log(iterator.next()) // { value: undefined, done: true }

只有一个对象实现了 Symbol.iterator 属性时,我们才认为它是可迭代的。一些内置的类型如 Array,Map,Set,String,Int32Array,Uint32Array 等都已经实现了各自的 Symbol.iterator。

Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的迭代器生成函数。执行这个函数,就会返回一个迭代器。

比如,String 是一个内置的可迭代对象:

let str: string = 'Hi'
console.log(typeof str[Symbol.iterator]) // function
最后更新: 9/8/2022, 8:51:57 AM