接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做 “鸭式辨型法” 或 “结构性子类型化”。 在 TypeScript 里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
下面通过一个简单示例来观察接口是如何工作的
function printLabel(labeledObj: { label: string }) {
console.log(labeledObj.label);
}
let myObj = {
label: "Size 10 Object",
};
printLabel(myObj);
上边的例子中,我们定义了一个函数,函数的参数类型是一个对象类型,而参数,也就是对象中有一个 lable 属性,我们定义了 label 属性为string
类型,如果对象中是单一参数,那代码还具有可读性,可是如果参数是很多个,则大大降低了可读性
修改为接口处理参数形式
interface LabeledValue {
label: string;
age: number;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = {
label: "Size 10 Object",
number: 10,
};
printLabel(myObj);
提示
在接口中定义了两个属性的类型,函数中虽然没有使用 age,但是在 myObj 中必须要实现 age
可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。 可选属性在应用 “option bags” 模式时很常用,即给函数传入的参数对象中只有部分属性赋值了
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ?
符号
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig) {
let newSquare = {
color: "white",
area: 100,
};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: "black" });
console.log(mySquare);
相关信息
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 比如,我们故意将 createSquare 里的 color 属性名拼错,就会得到一个错误提示// Error: Property 'clor' does not exist on type 'SquareConfig'
只读属性
一些对象属性只能在对象刚刚创建的时候赋值。 你可以在属性名前用 readonly
来指定只读属性
对象操作
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
函数操作
interface Point {
readonly x: number;
readonly y: number;
}
function MyPoint(ps: Point) {
ps.x = 10000; // 无法为“x”赋值,因为它是只读属性。
ps.y = 20000; // 无法为“x”赋值,因为它是只读属性。
console.log(ps);
}
MyPoint({ x: 100, y: 200 });
readonly vs const
最简单判断该用 readonly
还是 const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const
,若做为属性则使用 readonly
额外的属性检查
我们知道。接口具有“option bags” 模式,允许我们使用可选参数,那我们来看下面的例子
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig) {
console.log(config); // { width: 100 }
}
let mySquare = createSquare({ width: 100 });
现在多了一个需求,在传入的参数中,可能会传入未知参数colour
因为有“option bags” 模式,会不会允许我们传入未知参数呢?
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig) {
console.log(config);
}
let mySquare = createSquare({ colour: "red", width: 100 });
然而,TypeScript 会认为这段代码可能存在 bug
对象字面量会被特殊对待而且会经过“额外属性检查”,你会得到一个错误
我们可以通过添加额外属性的方式解决这个问题
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
function createSquare(config: SquareConfig) {
console.log(config); // { colour: 'red', width: 100 }
}
createSquare({ colour: "red", width: 100 });
函数类型
接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型
interface SearchFunc {
(source: string, subString: string): void;
}
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口
interface SearchFunc {
(source: string, subString: string): void;
}
let mySearch: SearchFunc = (str1: string, str2: string) => {
console.log(str1, str2);
};
mySearch("Hello", "World");
提示
对于函数类型的类型检查来说,函数的参数名可以不与接口里定义的名字相匹配
类类型
与传统面向对象里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约
interface UserInterface {
job: string;
}
class Teacher implements UserInterface {
public job: string = "";
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
console.log(age);
}
}
new Teacher("lcl", 10);
你也可以在接口中描述一个方法,在类里实现它
interface UserInterface {
job: string;
sayHello(j: string): void;
}
class Teacher implements UserInterface {
public job: string = ""; // 实现接口时必须要初始化
public name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
console.log(age);
}
sayHello(j: string) {
this.job = j;
console.log(this.job, this.name, this.age);
}
}
new Teacher("lcl", 10).sayHello("helllo");
继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里
interface Info {
name: string;
age: number;
}
interface Job extends Info {
job: string;
}
function user(userInfo: Job) {
console.log(userInfo);
}
user({ name: "iwen", age: 20, job: "web" });
一个接口可以继承多个接口,创建出多个接口的合成接口
interface Info {
name: string;
age: number;
}
interface Num {
num: number;
}
interface Job extends Info, Num {
job: string;
}
function user(userInfo: Job) {
console.log(userInfo);
}
user({ name: "iwen", age: 20, job: "web", num: 1001 });