blog icon indicating copy to clipboard operation
blog copied to clipboard

typescript 中的 interface 和 type 到底有什么区别?

Open SunshowerC opened this issue 5 years ago • 29 comments

  • interface VS type
    • 相同点
      • 都可以描述一个对象或者函数
        • interface
        • type
      • 都允许拓展(extends)
        • interface extends interface
        • type 与 type 相交
        • interface extends type
        • type 与 interface 相交
    • 不同点
      • type 可以而 interface 不行
      • interface 可以而 type 不行
  • 总结

interface VS type

大家使用 typescript 总会使用到 interface 和 type,官方规范 稍微说了下两者的区别

  • An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
  • An interface can have multiple merged declarations, but a type alias for an object type literal cannot. 但是没有太具体的例子。

明人不说暗话,直接上区别。

相同点

都可以描述一个对象或者函数

interface

interface User {
  name: string
  age: number
}

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

type

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

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

拓展(extends)与 交叉类型(Intersection Types)

interface 可以 extends, 但 type 是不允许 extends 和 implement 的,但是 type 缺可以通过交叉类型 实现 interface 的 extend 行为,并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 与 interface 类型 交叉 。

虽然效果差不多,但是两者语法不同

interface extends interface

interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

type 与 type 交叉

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

interface extends type

type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

type 与 interface 交叉

interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

不同点

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

interface 可以而 type 不行

interface 能够声明合并

interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

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

总结

一般来说,如果不清楚什么时候用interface/type,能用 interface 实现,就用 interface , 如果不能就用 type 。其他更多详情参看 官方规范文档

SunshowerC avatar Dec 29 '18 07:12 SunshowerC

type 那个不叫 extend ,那就是类型合并

type 关键字的产生的东西官方有一个名字 type aliases ,就是类型别名,重点是它是别名不是真正的类型

我发现大家好像都不是非常喜欢去看文档,其实这种基本的东西根本不用去看 spec ,文档里面就应该有 https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases ,它的设计意图,它的适用场景,它和 interface 的差异在文档里都明明白白的写清楚了,看完就好了

别的公司的东西不好说,微软出的很多东西文档都是写得非常好的,就去看文档就好了

bolasblack avatar Jan 14 '19 05:01 bolasblack

type 那个不叫 extend ,那就是类型合并

type 关键字的产生的东西官方有一个名字 type aliases ,就是类型别名,重点是它是别名不是真正的类型 ......

@bolasblack 我当然知道这是类型别名,但是虽然官方定义用途不同,但实际上 interface 和 type 在某些场景下差别不大,是可以通用的,不是么?当然,滥用 type 是不好的。这其实也和 HTML 的语义化有点类同,h1div + css 都能实现同样的样式效果,但是两者的语义是不一样的。

SunshowerC avatar Jan 14 '19 12:01 SunshowerC

@Weiyu-Chen 那你说说看,为什么滥用 type 是不好的……

bolasblack avatar Jan 14 '19 12:01 bolasblack

其实文档里已经明明白白地写了:

A second more important difference is that type aliases cannot be extended or implemented from (nor can they extend/implement other types).

这就是我为什么在开头要强调 type 的那个不叫 extend ,因为那个真的不是 extend ……

bolasblack avatar Jan 14 '19 12:01 bolasblack

我为了把 type 类比 interface 的 extend 行为,顺手就用 extend 了。确实,用 extend 来描述 类型别名确实不严谨,感谢勘误。 另外,“为什么滥用 type 是不好的”,我个人认为,除了type 和 interface 在行为上确实有一定差异之外,还有的是上述我提的语义化,能用 interface 描述一个类型,尽量不要用 type 描述。 官方文档也是建议尽可能使用 interface

you should always use an interface over a type alias if possible. On the other hand, if you can’t express some shape with an interface and you need to use a union or tuple type, type aliases are usually the way to go.

还有什么问题或者意见的欢迎提出来,共同探讨。 @bolasblack

SunshowerC avatar Jan 15 '19 08:01 SunshowerC

对总结表示不赞同。 interface是接口,type是类型,本身就是两个概念。只是碰巧表现上比较相似。 希望定义一个变量类型,就用type,如果希望是能够继承并约束的,就用interface。 如果你不知道该用哪个,说明你只是想定义一个类型而非接口,所以应该用type。

trlanfeng avatar Jan 17 '19 00:01 trlanfeng

@trlanfeng 这么理解倒也不能说错,但是 type/interface 到底怎么用这可能真的见仁见智了。 type 和 interface 除了官方定义不同之外,很多功能其实 用 type 和 interface 从结果上看没多大区别。

例如这篇文章interface-vs-type-alias就介绍说 type 和 interface 只要和团队保持统一就好,除了特定场景外,没有什么是必须用 interface/type 的。

  • type aliases can act sort of like interfaces, however, there are 3 important differences ( union types, declaration merging)
  • use whatever suites you and your team, just be consistent
  • always use interface for public API's definition when authoring a library or 3rd party ambient type definitions
  • consider using type for your React Component Props and State

SunshowerC avatar Jan 18 '19 07:01 SunshowerC

interface StringMap {
  [key: string]: string;
}
interface A {
  key?: string;
}

type B = {
  key?: string;
};
const a: A = { key: "1" };
const b: B = { key: "1" };
const c: StringMap = b;
const d: StringMap = a;

这段代码,执行const d的时候会有错误。提示「 不能将类型“A”分配给类型“StringMap”。 类型“A”中缺少索引签名。ts(2322)」,这个该如何解释呢?

yinshuxun avatar Jul 09 '19 07:07 yinshuxun

image

xgqfrms avatar Aug 24 '19 10:08 xgqfrms

mark,插眼。 顶上去,日后观察。

yanyang1116 avatar Jan 15 '20 03:01 yanyang1116

interface StringMap {
  [key: string]: string;
}
interface A {
  key?: string;
}

type B = {
  key?: string;
};
const a: A = { key: "1" };
const b: B = { key: "1" };
const c: StringMap = b;
const d: StringMap = a;

这段代码,执行const d的时候会有错误。提示「 不能将类型“A”分配给类型“StringMap”。 类型“A”中缺少索引签名。ts(2322)」,这个该如何解释呢?

写过c或者go这类语言就很容易明白type了 type就是type alias 也就是 go 里的type:type S = string;var s S 把B类型给b,再赋StringMap类型,实际上B就是一个别名 而A类型不同,A是一个接口,它实际上存在,并且与拥有索引签名的StringMap接口不兼容,所以报错

Liuyll avatar Feb 09 '20 10:02 Liuyll

// -------- base --------

type Data = Record<string, string | number>;

function fn(data: Data) {}

// -------- step one --------

type IGetUserServiceList = {
  id: string;
};

let fooData: IGetUserServiceList = {
  id: '12345',
};

fn(fooData);

// -------- step two --------

interface _IGetUserServiceList {
  id: string;
}

let _fooData: _IGetUserServiceList = {
  id: '12345',
};

// error
// 类型“_IGetUserServiceList”中缺少索引签名
fn(_fooData);

// 改为如下即可
interface __IGetUserServiceList {
  // 需要增加索引签名
  [k: string]: string | number;
  id: string;
}

借楼打扰,关于上面的这个问题,可以解释一下么,使用 type 和 interface 定义的都是相同的对象,但是使用 interface 的时候会提示缺少索引签名,难道是 type 定义对象的时候,已经默认给予了 [k:string]: string | number; 了?

感谢。

jsjzh avatar Apr 16 '20 03:04 jsjzh

留个名,刚开始学ts,很多不懂,向各位学习

negeafei avatar Apr 23 '20 06:04 negeafei

个人认为两种场景可能会使用到itype而不能用interface:

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

  2. 限定具体几个值的基本类型联合类型 type someAnimal = 'dog' | 'cat'

Alcantara6 avatar May 24 '20 09:05 Alcantara6

个人认为两种场景可能会使用到itype而不能用interface:

  1. 具体定义数组每个位置的类型 type PetList = [Dog, Pet]
  2. 限定具体几个值的基本类型联合类型 type someAnimal = 'dog' | 'cat'

高见

alowkeyguy avatar Jun 15 '20 02:06 alowkeyguy

cy typeVSinterface

Eating-Eating avatar Aug 06 '20 08:08 Eating-Eating

m

HomyeeKing avatar Aug 28 '20 06:08 HomyeeKing

@jsjzh 我理解是这样的,借用官方文档的话 interface 可以Adding new fields to an existing interface,而一个type,A type cannot be changed after being created。

就你的例子来说,IGetUserServiceList 由type创建,它的 shape已经固定,但是_IGetUserServiceList完全可以在后面再加一个boolean属性:

interface _IGetUserServiceList {
  rich: boolean;
}

从而导致_IGetUserServiceList最终的shape是下面这样:

interface _IGetUserServiceList {
  rich: boolean;
  id: string;
}

这时 _IGetUserServiceListData 的shape是不一致的。而加入索引签名后,就可以防止_IGetUserServiceList被加入string之外的属性,也就保证了和Data的shape一致。

interface __IGetUserServiceList {
  // 增加索引签名后不能再加入boolean属性
  [k: string]: string | number;
  id: string;
}
interface __IGetUserServiceList {
  rich: boolean  // error
}

总结一下,type关键字声明的type在创建后是不可变的。而interface在创建后是可变的,但是这种可变性可以通过索引签名来约束。

imgss avatar Nov 03 '20 03:11 imgss

个人角度,type alias 语法上更像代数数据类型,或许会受到喜欢函数式编程的程序员青睐。例如 type student = [string, number]; 可以看做元组,type student = {name: string, old: number} 可以看做命名元组。 a & b 可以看做 a 和 b 类型的积(product), a | b 可以看做 a 和 b 类型的和(sum)。

SyMind avatar Dec 14 '20 12:12 SyMind

interface StringMap {
  [key: string]: string;
}
interface A {
  key?: string;
}

type B = {
  key?: string;
};
const a: A = { key: "1" };
const b: B = { key: "1" };
const c: StringMap = b;
const d: StringMap = a;

这段代码,执行const d的时候会有错误。提示「 不能将类型“A”分配给类型“StringMap”。 类型“A”中缺少索引签名。ts(2322)」,这个该如何解释呢?

type TestA = A extends StringMap ? A : never

// TestA = never

type TestB = B extends StringMap ? B : never

// TestB = B

hjwzzz avatar Feb 23 '21 07:02 hjwzzz

两者都是为了告诉编译器,如何理解某个字段的结构类型

但两者的定义和使用场景还是有区别的:

  • interface 是一种关系结构的描述,里面可以包含属性和方法,可派生
  • type 是一种表达式,所以也可以说是一种aliase,可以使用一些表达式的操作符,并且通过这些操作符实现和interface近似等价的关系描述

所以,在描述带关系的数据结构时,interface应该优先于type被考虑,甚至可以简化思考,直接上interface。而type在一定程度上简化类型描述,例如,type StrOrNum = string | number,后面都可以复用StrOrNum去代表string | number,如果在一个类型描述文件里,string | number这样的类型字段比较多,就可以用type去精简内容。

forcs avatar Mar 12 '21 06:03 forcs

我觉得创造类型的与集使用interface,使用类型的或集使用type。与集与其说是类型,更应该说是类,只不过这里不需要实例(或者说不需要prototype)。

gonlyk avatar Sep 30 '21 10:09 gonlyk

type 那个不叫 extend ,那就是类型合并 type 关键字的产生的东西官方有一个名字 type aliases ,就是类型别名,重点是它是别名不是真正的类型 ......

@bolasblack 我当然知道这是类型别名,但是虽然官方定义用途不同,但实际上 interface 和 type 在某些场景下差别不大,是可以通用的,不是么?当然,滥用 type 是不好的。这其实也和 HTML 的语义化有点类同,h1div + css 都能实现同样的样式效果,但是两者的语义是不一样的。

尽量多用type可以省去很多麻烦

akriafly avatar Oct 14 '21 23:10 akriafly

type 那个不叫 extend ,那就是类型合并 type 关键字的产生的东西官方有一个名字 type aliases ,就是类型别名,重点是它是别名不是真正的类型 ......

@bolasblack 我当然知道这是类型别名,但是虽然官方定义用途不同,但实际上 interface 和 type 在某些场景下差别不大,是可以通用的,不是么?当然,滥用 type 是不好的。这其实也和 HTML 的语义化有点类同,h1div + css 都能实现同样的样式效果,但是两者的语义是不一样的。

尽量多用type可以省去很多麻烦

官网文档说尽可能使用interface, image

ChenKun1997 avatar Feb 14 '22 13:02 ChenKun1997

mark 我是来学习的

JuctTr avatar Aug 05 '22 07:08 JuctTr

不觉得是好的设计,我认为只保留type就可以了。类型合并这种场景很少,用了也容易滋生bug

happy-little-one avatar Sep 29 '22 04:09 happy-little-one

从写后端角度。比如:go,给我的感觉是: type 类似于 结构体。 interface 类似于 接口 比较抽象。

localhostjason avatar Feb 13 '23 07:02 localhostjason

个人感觉 typeinterface 好用一点🙈

wmasfoe avatar Mar 16 '23 06:03 wmasfoe

如果你是一个人写只给自己看的代码,那可以闭着眼睛只用interface。如果写的代码会给别人看,那就用type。下列所谓的interface合并功能明显是违反OCP的:

interface User {
  name: string,
}

/*
User interface itself now has been changed!
*/
interface User {
  sex: string,
}

这跟js中从头到尾用一个变量a来接收所有值是一个性质——只要不用维护,怎么写都是你的自由。

事实上,interface的能力比type强大(除了起别名),这就是为什么文档中说:

If you would like a heuristic, use interface until you need to use features from type.

但是如果用不到这些强大的能力,那么还是用type更好一点。

tonny1983 avatar Feb 04 '24 04:02 tonny1983