接口(Interface)
- 用来建立某种代码约定,使得其它开发者在调用某个方法或者创建新的类时,必须遵循接口所定义的代码约定
- 接口的前面加了一个 I 字母
- 规范
- 在代码设计中,接口是一种规范;
- 接口通常用于来定义某种规范, 类似于你必须遵守的协议,
- 站在程序角度上说接口只规定了类里必须提供的属性和方法,
- 从而分离了规范和实现,增强了系统的可拓展性和可维护性;
示例
const getUserInfo = function (user) {return `name: ${user.name}, age: ${user.age}`;
};getUserInfo({ name: 'koala', age: 18 });// 错误的调用
getUserInfo(); // Uncaught TypeError: Cannot read property 'name' of undefined
console.log(getUserInfo({ name: 'long' })); // name: long, age: undefined
getUserInfo({ name: 'long', height: 1.66 }); // name: long, age: undefined
- 如果这个 getUserInfo 在多人开发过程中,
- 如果它是个公共函数,多个开发者都会调用,
- 如果不是每个人点进来看函数对应注释,就可能出现调用问题
改版1
const getUserInfo = (user: { name: string, age: number }): string => {return `name: ${user.name} age: ${user.age}`;
};
getUserInfo({ name: 'long', age: 18 });// 错误的调用
// getUserInfo(); // 错误信息:An argument for 'user' was not provided.
// getUserInfo({ name: 'long' }); // 错误信息:Property 'age' is missing in type '{ name: string; }'
// getUserInfo({ name: 'long', height: 1.88 }); // 错误信息:类型不匹配
- 调用的时候,会直接提示错误
重构1
// 先定义一个接口interface IUser {name: string;age: number;}const getUserInfo = (user: IUser): string => {return`name: ${user.name}, age: ${user.age}`;};// 正确的调用getUserInfo({name: "long", age: 18});
重构2
type IUserInfoFunc = (user: IUser) =>string;interface IUser {name: string;age: number;}const getUserInfo: IUserInfoFunc = (user) => {return`name: ${user.name}, age: ${user.age}`;};getUserInfo({name: "koala", age: 18});// getUserInfo();
接口的实现
// 接口的实现,使用 implements 关键字interface Entity {title: string;log(): void;}class Post implements Entity {title: string;constructor(title: string) {this.title = title;}log(): void {console.log(this.title);}}
作为参数的类型声明,自定义一个类型.
// 声明一个接口interface IPerson {name: string;age: number;
}// type类似interface,以下写法等同用interface声明IPerson
type IPerson {name: string;age: number;
}class Person {constructor(public config: IPerson) {}
}// 再调用时,要满足接口的要求(不能增加,也不能减少)
var p1 = new Person({name: "张三";age: 18
})// interface 能够声明合并,Type不可以interface User {name: stringage: number
}interface User {sex: string
}/*
User 接口为 {name: stringage: numbersex: string
}
*/
函数类型
interface Animal {eat(food:string);
}class Sheep implements Animal {// 实现接口,必须要实现接口里的类eat(food:string) {console.log("wo chi cao");}
}interface Func {(x: number, y: number, desc?: string): void
}
<=>
type Func = (x: number, y: number, desc?: string) =>void;const sum: Func = function (x, y, desc = '') {
// const sum: Func = function (x: number, y: number, desc: string): void {// ts类型系统默认推论可以不必书写上述类型定义console.log(desc, x + y)
}
sum(32, 22)// 定义函数interface SearchFunc {(source: string, subString: string): boolean;}let mySearch: SearchFunc;// 该函数拥有和类型声明一致的参数类型和返回值。mySearch = function (source: string, subString: string) {let result = source.search(subString);return result > -1;};// 编译器会自动推断参数和返回值的类型let mySearch: SearchFunc;mySearch = function (src, sub) {let result = src.search(sub);return result > -1;};// 编译器推断返回值为 boolean 类型。// 如果你返回了错误的类型,编译器会报错let mySearch: SearchFunc;mySearch = function (src, sub) {// Type '(src: string, sub: string) => string' is not assignable to type 'SearchFunc'.// Type 'string' is not assignable to type 'boolean'.let result = src.search(sub);return 'string';};
可选属性
- 可有可无的,那么为什么还要定义呢?
- 对比起完全不定义,定义可选属性主要是
- 为了让接口更加的灵活,某些属性我们可能希望设计成可选,
- 并且如果存在属性,能约束类型,而这也是十分关键的
- 对比起完全不定义,定义可选属性主要是
增任意属性
insterface IPerson {name: string;age ?: number; // 可选属性sex ?: '男' | '女' // IPerson可选一个sex属性,值为'男'或者'女'或者undefined}[propName: string]: any; // 字符串类型的索引签名
}let user: IPerson = {name: 'mark',age: 1,work: 'web'
};// 如果任意类型定义为string 那么 上面代码会报错。
insterface IPerson {name: string;age ?: number;[propName: string]: string; //error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'
}// 可选属性,使用,先进行判断后,进行使用
if (user.age) {user.age
}
多余属性检查
interface SquareConfig {color?: string;width?: number;
}function createSquare(config: SquareConfig): { color: string; area: number } {return { color: config.color || "red", area: config.width || 20 };}// let mySquare = createSquare({ colour: "red", width: 100 }); // 报错// 报错原因 // TypeScript 认为这可能是一个 BUG。// 编译器在将对象字面量赋值给别的变量或者传递给某个函数时会有一个被称为***多余属性检查***的特殊过程。// 如果对象字面量有任何目标类型所没有的属性时,就会报错;// Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'.// Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write ‘color'// 跳过该检查// let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);// 如果非要 增加额外属性// 添加一个字符串类型的索引签名interface SquareConfig {color?: string;width?: number;[propName: string]: any;}
可索引的类型
// 接口除了可以给函数、对象声明类型,
// 还可以给任何有索引的对象声明类型,比如数组。
// 可索引的类型由索引签名来描述对象的类型interface StringArray {[index: number]: string;}// StringArray 拥有一个索引签名,这个签名的属性的类型是 number,而属性值的类型是 stringlet myArray: StringArray;myArray = ["Bob", "Fred"];let myStr: string = myArray[0];// 索引签名的属性的类型有两种,number 或者 string;// 当然你可以同时支持两种类型,// 但是使用数字来索引的属性的类型必须是使用字符串索引的类型的子类型,// 因为 JavaScript 在索引的时候,// 会将数字类型的属性转换为字符串类型。// 这就意味着索引 100 的类型应该和索引 "100" 的一致interface Animal {name: string;}interface Dog extends Animal {breed: string;}// Error: indexing with a numeric string might get you a completely separate type of Animal!interface NotOkay {// [x: number]: Animal;// Numeric index type 'Animal' is not assignable to string index type 'Dog'.[x: number]: Dog; // [x:number]:Dog , Dog 要和 Dog保持一致[x: string]: Dog;}// 只读类型的 可索引类型interface ReadonlyStringArray {readonly [index: number]: string;}let myArray: ReadonlyStringArray = ['Alice', 'Bob'];// myArray[2] = 'Mallory'; // error!// Index signature in type 'ReadonlyStringArray' only permits reading.
只读属性控制
interface IPerson {readonly id: number;name: string;age?: number;[index: number]: number; // 数组family: Array<string> // IPerson需要包含一个family属性,类型是数组,数组里面都是string类型的数据[propName: string]: any;
}let user: IPerson = {id: 1,name: 'mar;',work: ‘web’,family:[‘1’]
};
user.id = 2;
// 报错 Cannot assign to 'id' because it is a constant or a read-only property.// 只读数组let a: number[] = [1, 2, 3, 4];let ro: Readonly Array<number> = a;// ro[0] = 12; // error!// Index signature in type 'readonly number[]' only permits reading.// ro.push(5); // error!// Property 'push' does not exist on type 'readonly number[]'.// ro.length = 100; // error!// Cannot assign to 'length' because it is a read-only property.// a = ro; // error!// The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.// 通过类型断言来跳过该问题let a: number[] = [1, 2, 3, 4];let ro: Readonly Array<number> = a;a = ro as number[];
类类型
interface ClockInterface {currentTime: Date;
}class Clock implements ClockInterface {currentTime: Date = new Date();constructor(h: number, m: number) {}}// 定义方法interface ClockInterface {currentTime: Date;setTime(d: Date): void;}class Clock implements ClockInterface {currentTime: Date = new Date();setTime(d: Date) {this.currentTime = d;}constructor(h: number, m: number) {}}// 接口只能定义类的公开部分(公开属性、公开方法)// 而不能定义私有部分,因此你不能通过接口来约束类的私有实现
实例接口和静态接口
- 类有两种类型:
- 实例的类型
- 静态的构造函数的类型。
// 如果你用构造签名声明一个接口再让一个类去实现的话,就会报错interface ClockConstructor {new (hour: number, minute: number);
}
// 报错
class Clock implements ClockConstructor {// Class 'Clock' incorrectly implements interface 'ClockConstructor'.// Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'.currentTime: Date;constructor(h: number, m: number) {}
}
// 原因 当类实现一个接口的时候,类实现的是实例的接口(而非构造函数的)
// 定义了两个接口,// 构造函数类型 ClockConstructor 和实例类型 ClockInterface,// 再定义了一个创建实例的函数 createClock,该函数返回第一个参数的实例// 构造函数类型接口 ClockConstructorinterface ClockConstructor {new (hour: number, minute: number): ClockInterface;}// 实例类型 ClockInterfaceinterface ClockInterface {tick(): void;}/*** @description: 创建实例函数* @param {type} ctor:ClockConstructor* @return: ClockInterface的实例*/function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {return new ctor(hour, minute);}class DigitalClock implements ClockInterface {constructor(h: number, m: number) {}tick() {console.log('beep beep');}}class AnalogClock implements ClockInterface {constructor(h: number, m: number) {}tick() {console.log('tick tock');}}let digital = createClock(DigitalClock, 12, 17);let analog = createClock(AnalogClock, 7, 32);// createClock 的第一个参数应该拥有 ClockConstructor 类型,// 因此该函数会检查传递进来的 DigitalClock 和 AnalogClock 的构造函数是否是 ClockConstructor 类型
// 两个类型结合起来,就是通过类表达式(而非类声明)interface ClockConstructor {new (hour: number, minute: number);}interface ClockInterface {tick();}const Clock: ClockConstructor = class Clock implements ClockInterface {constructor(h: number, m: number) {}tick() {console.log('beep beep');}};// const Clock: ClockConstructor 声明了类的构造函数类型,// 而后面的 class Clock implements ClockInterface 则声明了类的实例类型。
继承
interface interfaceA {a:number;
}
interface interfaceB {b:string;
}
interface interfaceC extends interfaceA,interfaceB {c:boolean;
}let interC:interfaceC = <interfaceC>{};// 定义的同名属性的类型不同的话,是不能编译通过的interface Shape {color: string;test: number; // <-
}interface PenStroke extends Shape{penWidth: number;test: string; // <-
}
混合类型
interface Counter {(start: number): string;interval: number;reset(): void;}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;
继承自类的接口
// 当一个接口继承自类时,
// 这个接口就继承了类的所有成员和对应的类型,但是不会继承实现。
// 继承自类的接口甚至会继承类的 private 和 protected 属性,
// 因此一个继承自类的接口,只能由被继承的类或者其子类实现class Control {private state: any;}interface SelectableControl extends Control {select(): void;}class Button extends Control implements SelectableControl {select() {}}class TextBox extends Control {select() {}}class ImageControl implements SelectableControl {// Class 'ImageControl' incorrectly implements interface 'SelectableControl'.// Types have separate declarations of a private property 'state'.private state: any;select() {}}// SelectableControl 继承了 Control 的所有成员,包括 private 属性 state,// 因此能实现 SelectableControl 接口的类一定是继承自 Control 或其子类的类,// 因为只有 Control 中的 state 才是 SelectableControl 中定义的 state 的原始实现(即使在继承类中实现了 state 也不行,因为 state 是 Control 私有的)。// 实现了 SelectableControl 接口的实例可以通过 Control 访问私有属性 state。// 事实上,SelectableControl 就像 Control 有 select() 方法一样。// Button 和 TextBox 是 SelectableControl 的子类,因为他们继承自 Control 且拥有 select() 方法,// 而 ImageControl 有自己的 private 属性 state 而不是从 Control 继承,因此它就无法实现 SelectableControl 接口了
Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.https://serious-lose.notion.site/TS-Interface-439d14dd883148efbc23929fb1a7647d