Interface vs Type Alias

548 阅读4分钟

总览

Interface
Type Alias
可用来定义的对象object,array,function,class constructor可以用来表示所有的类型
继承或者扩充性通过 extends 继承通过类型交叉(Intersection Types)扩充
同名合并会同名合并
索引签名“静态索引签名”索引签名无限制
别名type就是用来作为别名来用的。

可用来定义的对象

Interface

1. 定义结构体和数组

interface Person {
    name: string;
    age: number;
}

interface Persons {
    name: string;
    age: number;
}[]

2. 定义函数

interface Func {
  (hour: string, minute: string): boolean;
}

3. 定义class constructor

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

4. 定义混合类型

正如我们之前提到的,接口可以描述现实世界 JavaScript 中存在的丰富类型。 由于 JavaScript 的动态和灵活的特性,您可能偶尔会遇到作为上述某些类型组合工作的对象。 一个这样的例子是一个既作为函数又作为对象的对象,具有附加属性:

interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}
declare let c: Counter
c(10);
c.reset();
c.interval = 5.0;

Type Alias

type这里表达能力拉满,没有什么限制。

1. 定义结构体和数组

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

2. 定义函数

type Func = (hour: string, minute: string) => boolean;

3.定义 Class constructor

type SomeConstructor = {
  new (s: string): SomeObject;
};

4. 定义混合类型

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
  new (s: string): Date;
};

其他任何你能想到的type

继承或者扩充性

Interface

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也可以继承某个class。这是由于class是有类型的。

class Shape {
    private color: string
}
interface PenStroke extends Shape {
    penWidth: number;
}

typescript中class相关内容也是很丰富,更多class相关内容后续单独补充。

Type Alias

type alias没有继承,但可以通过类型交叉来合并多个不同的type或者interface。

interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}
interface ArtistsData {
  artists: { name: string }[];
}
type ArtworksResponse = ArtworksData & ErrorHandling;

如果类型无法交叉,则结果为never

type Never = number & string

同名合并

Interface

interface Box {
 height: number;
 width: number;
}
interface Box {
 weight: number;
}
let box: Box = {height: 4, with: 5, weight: 6}

Interface和同名合并性在一些场景下特别有用。例如使用styled-jsx插件。写 会导致ts报错。这种情况下可以通过扩充React namespace中的StyleHTMLAttributes interface即可保证js不会报错。具体做法是在项目中新增d.ts文件。并且新增如下代码块。

namespace React {
  interface StyleHTMLAttributes<T> extends HTMLAttributes<T> {
    jsx?: boolean
  }
}

namespace和interface一样也存在同名合并,因此我们实际上是扩充了React namespace中StyleHTMLAttributes interface。

interface继承或者同名合并的情况下 如果存在同名属性类型不兼容或者是属性不符合索引签名,则会报错!

interface Colorful {
  color: string;
}
interface ColorRgb extends Colorful {
  opacity: number;
  color: number; // error!
}
interface Colorful {
  color: string;
  [k: string]: string;
}
interface Colorful {
  brightness: number; // error!
}

索引签名

Interface

interface中的members或者说是属性得是静态的(static)。不是说interface不支持范型,是interface的属性必须得是固定的。它的索引签名(index signature)必须得是静态的,不支持动态索引。

// 我们可以这么定义interface
interface Box<T> {
 height: number;
 width: number;
 weight: T
}
// 甚至可以这样定义
interface Foo {
  [index: number]: number;
  [k: `hello-${string}`]: unknown;
  // typescript4.4新增特性
}

但是下面这种写法在interface中是不合法的。

interface GenericType<T extends string, P> {
  [K in T as`get${K}`]: () => P // error!
}

Type Alias

type alias中对索引签名没有什么限制。可以最大化利用typescript中各种动态模板能力。

type GenericType<T extends string, P> = {
  [K in T as`get${Capitalize<K>}`]: () => P
}

type Hello = GenericType<'hello', number>
// getHello: () => number

type Foo<T> = {
  [index: number]: T;
  [k: `hello-${string}`]: unknown;
}
const a: Foo<number> = {
  2: 2333,
  'hello-world': {}
}

别名

Type Alias

字如其名,Type Alias是type别名。可以指代别的type。interface没有这种能力。 例如

type A = number
type B = Array<string>
interface Obj {
  [k: string]: unknown
}
type C = Obj
// 而 interface 没有 “=” 的操作 不能指代其他类型 只有继承

小测试

测试1

下面这个代码块会报错,尝试给出正确解释

function foo(param: Record<string, unknown>) {
}

interface Params {
  x: number;
  y: string;
}

declare const a: Params

foo(a)

答案 TypeScript playground

测试2

还是上面那个代码 我修改一个写法,测试不会报错 给出解释

function foo(param: Record<string, unknown>) {
}
type Params = {
  1: number;
  2: string;
}
declare const a: Params
foo(a)

答案 TypeScript

总结

Interface 总体来讲还是适合作为“接口”定义,class中用的比较多。其他场景建议一律用Type Alias,遇到的坑和限制会少一些。