天天看点

接口_TypeScript笔记3

感谢支持ayqy个人订阅号,每周义务推送1篇(only unique one)原创精品博文,话题包括但不限于前端、Node、Android、数学(WebGL)、语文(课外书读后感)、英语(文档翻译)        
   如果觉得弱水三千,一瓢太少,可以去 http://blog.ayqy.net 看个痛快               

写在前面

对于对象等复杂结构的类型,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.const与readonly的区别在于前者用来约束变量,后者用来约束属性(变量声明之外的场景)

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

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

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

// 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个东西进行检查:

参数类型

返回值类型

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

二.数组

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

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

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

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

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

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;
}
           

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

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;
}