教程3:Typescript的类型别名及接口

493 阅读4分钟

前情提要

# 教程1:花费一分钟,跟着做,傻瓜式入门Typescript

# 教程2:Typescript的变量类型介绍

类型别名

其实就是给类型换个名字,type 类型别名 = 变量类型

例如:

type str = string
let name: str //等同于 let name: string

如果多处的类型注解为相同的对象类型或者联合类型,多次重复书写可能会很繁琐。此时类型别名可以减少我们的重复操作。

// 不使用类型别名
function fn1(obj: { id: string | number, name: string }) {...}
function fn2(obj: { id: string | number, name: string }) {...}
function fn3(id: string | number) {...}
​
// 使用类型别名
type Id = string | number // 将联合类型string | number改名为Id
type People = { id: ID, name: string } // 将该对象改名为People
function fn1(obj: People) {...}
function fn2(obj: People) {...}
function fn3(id: Id) {...}

还可以这样使用:

type Animal = { name: string }
type Dog = Animal & { sayHello: boolean } // 交叉类型:将多个类型合并为一个类型
// 即Dog为{ name: string, sayHello: boolean } 该对象的别名  

接口

定义对象类型

// 使用接口,定义一个对象类型为People
interface People { 
    name: string;
}
function fn1(obj: People) {
    console.log(obj.name)
}
​
fn1({ id: 1, name: 'lemon' }) // 报错,id不在People类型内
fn1({ name: 'lemon' }) // 符合,执行函数时的实参符合People类型

可选属性

interface People { 
    name: string;
    age?: number; // 可选属性,接口内的age属性不是必需的
}
function fn1(obj: People) {...}
​
​
fn1({ name: 'lemon' }) // 符合,执行函数时的实参可以没有age属性

只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性:

interface Point {
    readonly x: number;
    readonly y: number;
}
​
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

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

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

扩展接口的方式

  1. 通过继承
interface Animal {
    name: string;
}
interface Dog extends Animal{
    sayHello: boolean;
}
/* 定义Dog接口,同时继承Animal接口。相当于如下写法:
    interface Dog{
        name: string;
        sayHello: boolean;
    }
*/
// 使用Dog接口类型
let dog: Dog = {
    name: 'wangcai',
    sayHello: true
}
  1. 允许多次定义该接口
interface Dog{
    name: string;
}
interface Dog{
    sayHello: boolean;
}
/* 最终会声明合并成一个接口,即如下:
    interface Dog{
        name: string;
        sayHello: boolean;
    }
*/

定义接口类型别名功能有些重叠,建议从它们本身的概念出发去理解即可。实际开发中均可使用的情况下,不必吹毛求疵(具体差异写在最后)。

定义函数类型

// 规定函数的形参类型跟返回值类型
interface SearchFn {
  (source: string, target: string): boolean;
}
​
let ifExist: SearchFn = function(str: string, val: string) {
  return str.indexOf(val) !== -1
}

定义可索引的类型

interface *** {
  [索引名: 索引类型]: 索引对应的值类型;
}

举例:定义数组

interface StringArray {
  [index: number]: string;
}
​
let myArray: StringArray;
myArray = ["Bob", "Fred"];
​
let myStr: string = myArray[0]; // 'Bob'

扩展

TypeScript支持两种索引签名:字符串和数字。

可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型

这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}
​
// 错误:数字索引的返回值必须是字符串索引返回值类型的子类型
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}

字符串索引签名能够很好的描述dictionary模式,并且它们也会确保所有属性与其返回值类型相匹配。

interface NumberDictionary {
  [index: string]: number;
  length: number;    // 可以,length是number类型
  name: string       // 错误,`name`的类型与索引类型返回值的number类型不匹配
}

最后,你可以将索引签名设置为只读,这样就防止了给索引赋值:

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!你不能设置myArray[2],因为索引签名是只读的

混合类型

接口能够描述JavaScript里丰富的类型。 因为JavaScript其动态灵活的特点,有时你会希望一个对象可以同时具有上面提到的多种类型。

举例:定义类数组

interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

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

举例

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;
// 在使用JavaScript第三方库的时候,你可能需要像上面那样去完整地定义类型。

接口 vs. 类型别名

  1. 类型别名不能被 extendsimplements(自己也不能 extendsimplements其它类型)
  2. 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。

辛勤的前端园丁,立志于把每个知识点嚼碎了喂你嘴里!

下篇文章:类与继承,马不停蹄整理中...