先让我们看看官方规范对于二者的解释
- 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:接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
接口就好比一个名字,用来描述上面例子里的要求。
接口具有的特性:
- 可选属性
interface SquareConfig {
color?: string;
}
- 只读属性
interface Point {
readonly x: number;
}
- 多余属性检查,防止使用不属于接口的属性
interface Preson {
name: string;
age?: number;
}
let p1:Person = {name: '小明'} // 正确
let p2:Person = {name: '小明', age: 18, sex: '男'}; // 报错
// 绕过:多余属性不报错
// 方式1
let p = {name: '小明', age: 18, sex: '男'};
let p3 = p;
// 方式2
interface Preson {
name: string;
age?: number;
[propName: string]: any
}
let p4 = {name: '小明', age: 18, sex: '男'};
- 函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
- 索引类型: 针对数组
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
-
类类型
- 类实现接口
interface ClockInterface { currentTime: Date; setTime(d: Date); } class Clock implements ClockInterface { currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } }
- 接口继承接口,可多个
interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; } let square = <Square>{}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0;
type:类型别名
type 会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。
举例:
type Name = string; // 基本类型
type NameResolver = () => string; // 函数
type NameOrResolver = Name | NameResolver; // 联合类型
type NameObj = { // 2.1以后可以定义新的类型
name: string,
age: number,
}
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:
type Container<T> = { value: T };
也可以使用类型别名来在属性里引用自己:
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。
type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
然而,类型别名不能出现在声明右侧的任何地方。
type Yikes = Array<Yikes>; // error
interface vs type
官方的文档说:
"类型别名 可以和interface关键字一样,然而他们有一些细微的差别。"
第一个不同
interface能够创建一个新的类型,type不能创建一个新的类型
这是不对的!(自从Typescript 2.1开始,type可以像interface一样创建一个新的类)
举个例子,创建一个type,定义编译时类型person
type persontype = {
name: string,
age: number,
};
const person: persontype = {
name: 'xiaoming',
age: 18,
};
console.log(person);
第二个不同
"第二个更加重要的重要的区别是类型别名不能被继承或者实现"
这个同样也是错的
我们能够通过一个interface继承或者类型别名:
type persontype = {
name: string;
age: number;
};
interface dimesions extends persontype {
tel: string;
}
class person implements persontype {
name = 'xiaoming';
age = 18;
}
我们也能通过组合类型别名和interface去实现类。
第三个不同
类型别名不能继承其他的类型别名或者接口
当然这个也是错误的
- 嗯,这个是部分正确的,但是这个表述具有误导性。
类型别名能通过交叉类型运算符&扩展interface或者任意有效的Typescript类型。但是不可以使用extends关键字去扩展。
所以类型别名type和interface到底有什么区别呢?
1. 语法不同
两者都可以用来描述对象或函数的类型,但是语法不同。
- Interface
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
- Type
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
2. interface可以定义多次,并将被视为单个接口
与类型别名不同,接口可以定义多次,并将被视为单个接口(合并所有声明的成员)。
// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };
3. extends方式不同
两者都可以扩展,但是语法又有所不同。此外,请注意接口和类型别名不是互斥的。接口可以扩展类型别名,反之亦然。
Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }
Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };
Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }
Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4. Type可以用于更多的类型
与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
// dom
let div = document.createElement('div');
type B = typeof div;
5. Type可以计算属性,生成映射类型
type 能使用 in 关键字生成映射类型,但 interface 不行。
语法与索引签名的语法类型,内部使用了 for .. in。 具有三个部分:
- 类型变量 K,它会依次绑定到每个属性。
- 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合。
- 属性的结果类型。
type Keys = "firstname" | "surname"
type DudeType = {
[key in Keys]: string
}
const test: DudeType = {
firstname: "Pawel",
surname: "Grzybek"
}
// 报错
//interface DudeType2 {
// [key in keys]: string
//}
我应该如何使用React的Props和State?
一般的,你要使用的一致就可以(无论使用类型别名还是interface),就我个人而言,我还是推荐使用类型别名:
- 书写起来更短
type Props = {}
- 你的用法是统一的(你不用为了类型交叉而interface和类型别名混用)
// BAD
interface Props extends OwnProps, InjectedProps, StoreProps {}
type OwnProps = {...}
type StoreProps = {...}
// GOOD
type Props = OwnProps & InjectedProps & StoreProps
type OwnProps = {...}
type StoreProps = {...}
复制代码
- 你组件公开的props/state不能被动态替换(译者注:因为原文作者这里说的 monkey patched翻译为动态替换 what-is-monkey-patching),就这个原因,你组件的使用者就不能利用interface的声明合并。对于扩展应该有像HOC这样明确的模式。
注意
不能使用类型别名定义联合类型去实现类,这将要在编译的时候触发一个错误:
类型别名联合使用用于定义对象是有意义并且有效的。所以下面会在编译时报一个错误,因为对象必须去定义perimeter()和shape()两个中的一个。
总结
interface 和 type 很像,很多场景,两者都能使用。但也有细微的差别:
不同点:
- 扩展语法: interface使用extends,type使用‘&’
- 同名合并:interface 支持,type 不支持。
- 描述类型:对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。
- 计算属性:type 支持计算属性,生成映射类型,;interface 不支持。
相同点:
- 两者都可以用来描述对象或函数的类型
- 两者都可以实现继承
总的来说,公共的用 interface 实现,不能用 interface 实现的再用 type 实现。主要是一个项目最好保持一致。