接口

接口的简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//声明一个Student类,他的构造函数接受一个实现People接口的对象
class Student {
everyDay:string
constructor(people:People){
this.everyDay=`一个学生每天都要${people.eat}和${people.sleep}`
}
}
//声明一个接口
interface People{
eat:string,
sleep:string
}
//创建一个实现了该接口的对象
let p = {eat:'吃饭',sleep:'睡觉'};
//通过该对象对stu进行初始化
let stu=new Student(p);
console.log(stu.everyDay)

与C#对比

在ts中,只要实现了接口属性的对象就可以在需要这个接口的地方使用:

1
2
3
4
5
6
7
8
9
interface Computer{
name:string,
size:number
}
function fun(p:Computer){
console.log(`一台${p.size}寸的${p.name}电脑`)
}
fun({name:"苹果",size:14});

C#的接口不能单独使用,接口作为抽象类的进一步抽象,需要由实体类继承才可使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
internal class Program
{
static void Main(string[] args)
{
Computer computer = new AppleComputer() { Name="苹果",Size=14};
Show(computer);
}
static void Show(Computer computer)
{
Console.WriteLine($"一台{computer.Size}寸的{computer.Name}电脑");
}
}
internal class AppleComputer : Computer
{
public string Name { get; set; }
public double Size { get; set; }
}
internal interface Computer
{
string Name { get; set; }
double Size { get; set; }
}

只读属性

1
2
3
4
5
6
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

1
2
3
4
5
6
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

上面代码的最后一行,可以看到就算把整个ReadonlyArray赋值到一个普通数组也是不可以的。 但是你可以用类型断言重写:

1
a= ro as number[];

额外检测

如果一个接口不包含某些属性,但是依然传入了进去,就会得到一个错误:

1
2
3
4
5
6
7
8
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): void {
console.log(config.color);
}
createSquare({color:'白色',width:14,other:'xxxx'})

这个错误的原因和赋值object类型的变量一样,解决方法:

1
2
3
4
5
6
7
8
//1、类型断言
createSquare({color:'白色',width:14,other:'xxxx'} as SquareConfig);
//2、更改接口为
interface SquareConfig {
color?: string;
width?: number;
[propName:string]:any
}

TS接口的函数类型与C#委托

函数类型

ts的接口可以类似C#中的委托,可以通过定义ts接口来实现控制函数格式的效果

1
2
3
4
5
6
7
interface funInterface {
(a:string,b:number):boolean
}
let fun:funInterface=(a:string,b:number)=>{
return true;
}
fun('sdf',15)

与C#对比

C#实现ts接口的函数类型是通过委托实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
internal class Program
{
static void Main(string[] args)
{
FunDelegate funDelegate = new FunDelegate(Fun);
funDelegate.Invoke("adsfas", 15);

}
static bool Fun(string a,double b)
{
return true;
}
}
public delegate bool FunDelegate(string a,double b);

TS接口的可索引的类型与C#索引器

可索引类型

ts的接口可以类似C#中的索引器,可以通过定义ts接口来实现通过索引获取数据的效果

1
2
3
4
5
6
interface StringArray {
[index:number]:string
}
let strArr:StringArray;
strArr=['hello','world','sdfasdf'];
console.log(strArr[1]);

与C#对比

C#实现ts接口的可索引类型是通过索引器实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
internal class Program
{
static void Main(string[] args)
{
StringArray stringArray = new StringArray();
Console.WriteLine(stringArray[0]);

}
}
internal class StringArray
{
public string[] Arr=new string[] {"hello","world","asdfasdf"};
public string this[int i]
{
get
{
return this.Arr[i];
}
set
{
this[i] = value;
}
}
}

拓展

支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致。
比如用数字索引去找动物可能返回一条狗这没问题
但是用数字索引去找狗可能返回一个动物这就有问题了:

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}

// Error: indexing with a 'string' will sometimes get you a Dog!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}

TS的类实现接口

和C#、Java一样接口可以由类实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface ClockInterface {
currentTime: Date;
ring(time:number):boolean;
}

class Clock implements ClockInterface {
currentTime: Date;
ring(time:number):boolean{
if(time===7)return true;
else return false
}
constructor(h: number, m: number) { }
}

接口进阶

扩展接口

接口可以继承接口:

1
2
3
interface Me extends Student {}
interface Student extends People{}
interface People{}

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
class Location {}

混合接口

一个接口可以同时做为函数和对象使用,并带有额外的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

接口解耦练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Phone{
call(message:string):void;
email(message:string):void
}
class ApplePhone implements Phone{
call(message:string):void{
console.log(`用苹果手机呼叫${message}`)
}
email(message: string): void {
console.log(`用苹果手机发送${message}`)
}
}
class People{
phone:Phone;
toSomeone(){
this.phone.call('你好')
this.phone.email('hello')
}
constructor(phone:Phone){
this.phone=phone
}
}
let applePhone=new ApplePhone();
let xiaohong=new People(applePhone);
xiaohong.toSomeone();