在接觸 ts 相關代碼的過程中,總能看到 interface 和 type 的身影。只記得,曾經遇到 type 時不懂查閱過,記得他們很像,相同的功能用哪一個都可以實現。但最近總看到他們,就想深入的瞭解一下他們。
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; // 聯合類型
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
1. Objects / Functions
兩者都可以用來描述對象或函數的類型,但是語法不同。
Interface
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
Type alias
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
2. Other Types
與接口不同,類型別名還可以用於其他類型,如基本類型(原始值)、聯合類型、元組。
// 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;
3. Extend
兩者都可以擴展,但是語法又有所不同。此外,請注意接口和類型別名不是互斥的。接口可以擴展類型別名,反之亦然。
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. class Implements
類可以以相同的方式實現接口或類型別名。但是請注意,類和接口被認爲是靜態的。因此,它們不能實現/擴展命名聯合類型的類型別名。
interface Point {
x: number;
y: number;
}
class SomePoint implements Point {
x: 1;
y: 2;
}
type Point2 = {
x: number;
y: number;
};
class SomePoint2 implements Point2 {
x: 1;
y: 2;
}
type PartialPoint = { x: number; } | { y: number; };
// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
x: 1;
y: 2;
}
5. extends class
類定義會創建兩個東西:類的實例類型和一個構造函數。 因爲類可以創建出類型,所以你能夠在允許使用接口的地方使用類。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
6. Declaration merging
與類型別名不同,接口可以定義多次,並將被視爲單個接口(合併所有聲明的成員)。
// 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 };
7. 計算屬性,生成映射類型
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
//}
7. 其他細節
export default interface Config {
name: string
}
// export default type Config1 = {
// name: string
// }
// 會報錯
type Config2 = {
name: string
}
export default Config2
總結
interface 和 type 很像,很多場景,兩者都能使用。但也有細微的差別:
- 類型:對象、函數兩者都適用,但是 type 可以用於基礎類型、聯合類型、元祖。
- 同名合併:interface 支持,type 不支持。
- 計算屬性:type 支持, interface 不支持。
總的來說,公共的用 interface 實現,不能用 interface 實現的再用 type 實現。主要是一個項目最好保持一致。
參考:
- 中文文檔的接口
- 中文文檔的高級類型
- 中文文檔的聲明合併
- Typescript: Interfaces vs Types
- TYPESCRIPT INTERFACE VS. TYPE