TS 笔记八 接口 type关键字

参考
https://github.com/zhongsp/TypeScript
TS入门篇 | 详解 TypeScript 接口类型

在JavaScript中,我们似乎很少听说接口这个概念,这是TypeScript中很常用的一个特性,它让 TypeScript 具备了 JavaScript 所缺少的、描述较为复杂数据结构的能力。

一、为什么要使用接口

来看例子,函数的参数是一个对象,它包含两个字段:firstName 和 lastName,返回一个拼接后的完整名字:

const getFullName = ({ firstName, lastName }) => {
  return `${firstName} ${lastName}`;
};

调用时传入参数:

getFullName({
  firstName: "Hello",
  lastName: "TypeScript"
}); 

这样调用是没有问题的,但是如果传入的参数不是想要的参数格式时,就会出现一些错误:

// Uncaught TypeError: Cannot destructure property `a` of 'undefined' or 'null'.
getFullName(); 
getFullName({ age: 18, phone: 110 }); // 'undefined undefined'
getFullName({ firstName: "Hello" }); // 'Hello undefined'

这些都是我们不想要的,在开发时难免会传入错误的参数,所以 TypeScript 能够在编译阶段就检测到这些错误。下面来完善下这个函数的定义:

const getFullName = ({
  firstName,
  lastName,
}: {
// 指定属性名为firstName和lastName的字段的属性值必须为string类型
  firstName: string; 
  lastName: string;
}) => {
  return `${firstName} ${lastName}`;
};

通过对象字面量的形式去限定传入的这个对象的结构,这些都是在编写代码时 TypeScript 提示的错误信息,这样就避免了在使用函数的时候传入不正确的参数。我们可以使用interface来定义接口:

interface Info {
  firstName: string;
  lastName: string;
}
const getFullName = ({ firstName, lastName }: Info) =>
  return `${firstName} ${lastName}`;
};

注意:在定义接口时,不要把它理解为是在定义一个对象,{}括号包裹的是一个代码块,里面是声明语句,只不过声明的不是变量的值而是类型。声明也不用等号赋值,而是冒号指定类型。每条声明之前用换行分隔即可,也可以使用分号或者逗号。

二、接口属性
1.可选属性

在定义一些结构时,一些结构的某些字段的要求是可选的,有这个字段就做处理,没有就忽略,所以针对这种情况,TypeScript提供了可选属性。

interface Vegetables {
  color?: string;
  type: string;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + " " : ""}${type}`;
};

这个函数中根据传入对象中的 color 和 type 来进行描述返回一句话,color 是可选的,所以可以给接口设置可选属性,在属性名后面加个?即可。

这里可能会报一个警告:接口应该以大写的i开头,可以在 tslint.json 的 rules 里添加"interface-name": [true, “never-prefix”]来关闭这条规则。

2.只读属性

接口可以设置只读属性,如下:

interface Role {
  readonly 0: string;
  readonly 1: string;
}

这里定义了一个角色,有 0 和 1 两种角色 id。下面定义一个角色数据,并修改一下它的值:

const role: Role = {
  0: "super_admin",
  1: "admin"
};
role[1] = "super_admin"; 
// Cannot assign to '0' because it is a read-only property

需要注意,readonly只是静态类型检测层面的只读,实际上并不能阻止对对象的修改。因为在转译为 JavaScript 之后,readonly 修饰符会被抹除。因此,任何时候与其直接修改一个对象,不如返回一个新的对象,这是一种比较安全的方式。

3.多余属性检查
interface Vegetables {
  color?: string;
  type: string;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + " " : ""}${type}`;
};

getVegetables({
  type: "tomato",
  size: "big"     // 'size'不在类型'Vegetables'中
});

这里没有传入 color 属性,因为它是一个可选属性,所以没有报错。但是多传入了一个 size 属性,这时就会报错,TypeScript就会提示接口上不存在这个多余的属性,所以只要是接口上没有定义这个属性,在调用时出现了,就会报错。

有时不希望TypeScript这么严格的对数据进行检查,比如上面的函数,只需要保证传入getVegetables的对象有type属性就可以了,至于实际使用的时候传入对象有没有多余的属性,多余属性的属性值是什么类型,我们就不管了,那就需要绕开多余属性检查,有如下方式:

a.使用类型断言
类型断言就是告诉 TypeScript,已经自行进行了检查,确保这个类型没有问题,希望 TypeScript 对此不进行检查,这是最简单的实现方式

interface Vegetables {
  color?: string;
  type: string;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + " " : ""}${type}`;
};

getVegetables({
  type: "tomato",
  size: 12,
  price: 1.2
} as Vegetables);

b.添加字符串索引签名

interface Vegetables {
  color: string;
  type: string;
  [prop: string]: any;
}

const getVegetables = ({ color, type }: Vegetables) => {
  return `A ${color ? color + " " : ""}${type}`;
};

getVegetables({
  color: "red",
  type: "tomato",
  size: 12,
  price: 1.2
});
三、接口定义函数类型
interface AddFunc {
  (num1: number, num2: number): number;
}

const add: AddFunc = (n1, n2) => n1 + n2;

// 不能将类型'string'分配给类型'number'
const join: AddFunc = (n1, n2) => `${n1} ${n2}`; 

// 类型'string'的参数不能赋给类型'number'的参数
add("a", 2); 

上面定义的add函数接收两个数值类型的参数,返回的结果也是数值类型,所以没有问题。而join函数参数类型没错,但是返回的是字符串,所以会报错。而当调用add函数时,传入的参数如果和接口定义的类型不一致,也会报错。在实际定义函数的时候,名字是无需和接口中参数名相同的,只需要位置对应即可。

四、interface 和 type 区别

参考
Typescript 中的 interface 和 type 到底有什么区别

1.相同点:都可以描述一个对象或者函数
interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}

type User = {
  name: string
  age: number
};

type SetUser = (name: string, age: number)=> void;

2.相同点:都允许拓展(extends)

interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。

interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
type Name = { 
  name: string; 
}
type User = Name & { age: number  };
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}
3.不同点:type 可以而 interface 不行

type 可以声明基本类型别名,联合类型,元组等类型

// 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

type 语句中还可以使用 typeof 获取实例的 类型进行赋值

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div

其他骚操作

type StringOrNumber = string | number;  
type Text = string | { text: string };  
type NameLookup = Dictionary<string, Person>;  
type Callback<T> = (data: T) => void;  
type Pair<T> = [T, T];  
type Coordinates = Pair<number>;  
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
4.interface 可以而 type 不行

interface 能够声明合并,即自动把多个同名的声明组合在一起。

interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

注意,两个接口不能有冲突,否则报错:

interface User {
  age: string
}
interface User {
  age: number
}
5.总结

一般来说,如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。

type 还可以定义字符串字面量类型,type x = 'a' | 'b' | 'c' 那么使用该类型只能从这三个值中取,不在的就会报错。另外使用type比较多的地方就是联合类型,如函数返回类型是type x = string | object | void,就不用一次次的写,复用性就高了。

6.literal-types.md
type Easing = "ease-in" | "ease-out" | "ease-in-out";

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    if (easing === "ease-in") {
      // ...
    } else if (easing === "ease-out") {
    } else if (easing === "ease-in-out") {
    } else {
      // It's possible that someone could reach this
      // by ignoring your types though.
    }
  }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy");
// Error: Argument of type '"uneasy"' is not 
assignable to parameter of type 'Easing'.

你可以传递三种允许的字符串,但是如果传递其他的字符串会收到如下错误:

Argument of type '"uneasy"' is not assignable to 
parameter of type '"ease-in" | "ease-out" | "ease-in-out"'

目前 TypeScript 中有三种可用的字面量类型集合,分别是:字符串、数字和布尔值。通过使用字面量类型,你可以规定一个字符串、数字或布尔值必须含有的确定值。

interface ValidationSuccess {
  isValid: true;
  reason: null;
};

interface ValidationFailure {
  isValid: false;
  reason: string;
};

type ValidationResult =
  | ValidationSuccess
  | ValidationFailure;
五、类实现接口
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 Animal{
    readonly name:string;
    eat(food:string):void;
    sleep(hours:number):void;
}

interface Feline{
    meow():void;
}

class Cat implements Animal,Feline{
    name = 'Whiskers';

    eat(food: string): void {
        console.log('Ate some',food,'.Mmm!');
    }

    sleep(hours: number): void {
      console.log('slept for',hours,'hours');  
    }

    meow(): void {
        console.log('meow');
    }
}
六、继承多个接口
interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
七、混合,接口中同时有属性和方法
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/public),也不能使用static关键字,不过可以使用readonly。

八、实现接口还是抽象类

实现接口与实现抽象类差不多。区别是,接口更通用更轻量,抽象类更具体、功能更丰富。

接口是对结构建模的方式。在值层面可表示对象、数组、函数、类或类的实例。接口不生成JS代码,只存在于编译时。

抽象类只能对类建模,而且生成运行时代码,即JS类。抽象类可以有构造方法,可以提供默认实现,还能为属性和方法设计访问修饰符。这些在接口中都做不到。

具体使用哪个,取决于实际用途。如果多个类共用同一个实现,使用抽象类。如果需要一种轻量的方式表示这个类是T型,使用接口。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容