接口_TypeScript笔记3

写在前面

对于对象等复杂结构的类型,TypeScript的理念是鸭子类型(duck typing),即值的“形状”:

Type-checking focuses on the shape that values have.

TypeScript里,通过接口来描述复杂结构的类型,例如:

interface LabelledValue {
    label: string;
}
function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}

// 等价于
function printLabel(labelledObj: { label: string }) {
    console.log(labelledObj.label);
}

这里接口的概念不同于其它语言,不必显式实现,只表示一种类型约束

一.对象

可选属性

紧跟着属性名的?表示可选,类似于正则表达式中?的含义,例如:

interface SquareConfig {
    color?: string; // 颜色可选
    width: number;  // 宽度必填
}

声明可选的意义在于,不使用没关系,用的话约束其类型正确:

// 正确
let squareConfig: SquareConfig = {width: 10};
// 错误:Type '10' is not assignable to type 'string'.
squareConfig.color = 10;

特殊的:

// 错误:Type '{ colour: string; width: number; }' is not assignable to type 'SquareConfig'.
let a: SquareConfig = { colour: "red", width: 100 };

拼写有误的colour之所以能被发现,是因为会检查对象字面量身上的多余属性

If an object literal has any properties that the “target type” doesn’t have, you’ll get an error.

但这种检查只针对字面量,因此:

let squareOptions = { colour: "red", width: 100 };
// 正确,不检查变量squareOptions身上的多余属性
let a: SquareConfig = squareOptions;

索引签名

有些场景下无法确定属性名称,例如:

let cache: NetCache = {};
cache['http://example.com'] = 'response';
cache['http://example.com/second'] = 'response';

允许NetCache类型的对象具有任意多个名为字符串的属性,此时可以通过索引签名(index signature)来描述这种类型约束:

interface NetCache {
    [propName: string]: string;
}

只读属性

interface Point {
    readonly x: number;
    readonly y: number;
}

紧跟在属性名前的readonly表示只读,与const约束一样,修改只读属性会抛出编译错误:

let p1: Point = { x: 10, y: 20 };
// 错误:Cannot assign to 'x' because it is a read-only property.
p1.x = 5;

P.S.constreadonly的区别在于前者用来约束变量,后者用来约束属性(变量声明之外的场景)

特殊的,只读数组有一种特别的类型表示ReadonlyArray<T>

let ro: ReadonlyArray<number> = [1, 2, 3, 4];
// 都会引发编译报错
ro[0] = 5;
ro.push(5);
ro.length = 1;

限制了其它所有会让数组内容发生变化的方式,还去掉了原型上的修改方法(poppushreverseshift等),因此不允许把只读数组赋值给普通数组:

// Type 'ReadonlyArray<number>' is missing the following properties from type 'number[]': pop, push, reverse, shift, and 6 more.
let arr: number[] = ro;

P.S.非要赋值的话,可以通过类型断言来做(let a: number[] = ro as number[]

另外,readonly也可以结合索引签名使用,例如:

interface NetCache {
    readonly [propName: string]: string;
}

用来约束属性值初始化之后无法修改:

let cache: NetCache = { 'http://example.com': 'response' };
// Index signature in type 'NetCache' only permits reading.
cache['url'] = 'response';

二.函数

接口也能用来表示函数类型,通过调用签名(call signature)来描述:

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

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

函数类型会对2个东西进行检查:

  • 参数类型

  • 返回值类型

注意,参数名不必完全匹配(不要求参数名一定是sourcesubString,按参数位置依次检查)

三.数组

数组的类型也可以用接口表示,例如:

interface StringArray {
    [index: number]: string;
}

let myArray: StringArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

没错,就是索引签名,不仅能表示一类属性,还能描述数组。之所以叫索引签名,是因为它能够描述可索引值的类型,例如StringArray表示能够通过数值索引访问字符串值

注意,只有两种合法的索引签名,分别是stringnumber,并且二者不能同时出现:

interface NotOkay {
    // Numeric index type 'boolean' is not assignable to string index type 'string'.
    [x: number]: boolean;
    [x: string]: string;
}

这是因为JavaScript中数值索引会被转换成字符串索引:

// JavaScript
const a = [1, 2, 3];
a[1] === a['1'] // true

四.类

与其它语言一样,类与接口之间有实现(implements)关系:

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

接口就像一种协议(或者说是契约),用来描述类的公开成员:

Explicitly enforcing that a class meets a particular contract.

P.S.构造函数的类型也能用接口描述,具体见Difference between the static and instance sides of classes

五.接口继承

接口可以通过继承的方式来扩展,例如:

interface Shape {
    color: string;
}
// 通过继承获得color属性
interface Square extends Shape {
    sideLength: number;
}

同样,也支持多继承:

interface PenStroke {
    penWidth: number;
}
// 多继承
interface Square extends Shape, PenStroke {
    sideLength: number;
}

通过继承建立的这种层级关系有助于组织有关联的接口,实现拆分、复用

P.S.特殊的,接口可以继承自类,相当于把该类的所有类型声明(包括私有属性)抽出来作为接口,用于约束子类,具体见Interfaces Extending Classes

六.混合类型

JavaScript里,函数也能像对象一样具有属性:

require('./utils');
delete require.cache[require.resolve('./utils')];

从类型上看,同时具有函数和对象的特征,称之为混合类型:

interface NodeRequireFunction {
    /* tslint:disable-next-line:callable-types */
    (id: string): any;
}

interface NodeRequire extends NodeRequireFunction {
    resolve: RequireResolve;
    cache: any;
    extensions: NodeExtensions;
    main: NodeModule | undefined;
}

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code