# 物件的型別——介面

在 TypeScript 中，我們使用介面（Interfaces）來定義物件的型別。

## 什麼是介面

在物件導向程式語言中，介面（Interfaces）是一個很重要的概念，它是對行為的一種抽象，而具體如何行動則需要由類別（classes）去實現（implement）。

TypeScript 中的介面是一個非常靈活的概念，除了可用於[對類別的一部分行為進行抽象](https://willh.gitbook.io/typescript-tutorial/basics/pages/-LpS2y9sevGthTpXALui#類別實現介面)以外，也常用於對「物件的形狀（Shape）」進行描述。

## 簡單的例子

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

let tom: Person = {
    name: 'Tom',
    age: 25
};
```

上面的例子中，我們定義了一個介面 `Person`，接著定義了一個變數 `tom`，它的型別是 `Person`。這樣，我們就約束了 `tom` 的形狀必須和介面 `Person` 一致。

介面一般首字母大寫。[有的程式語言中會建議介面的名稱加上 `I` 字首](https://msdn.microsoft.com/en-us/library/8bc1fexb%28v=vs.71%29.aspx)。

定義的變數比介面少了一些屬性是不允許的：

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

let tom: Person = {
    name: 'Tom'
};

// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.
```

多一些屬性也是不允許的：

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

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
```

可見，**賦值的時候，變數的形狀必須和介面的形狀保持一致**。

## 可選屬性

有時我們希望不要完全匹配一個形狀，那麼可以用可選屬性 (`?`)：

```typescript
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};
```

```typescript
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};
```

可選屬性的含義是該屬性可以不存在。

這時**仍然不允許新增未定義的屬性**：

```typescript
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
```

## 任意屬性

有時候我們希望一個介面允許有任意的屬性，可以使用如下方式：

```typescript
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
```

使用 `[propName: string]` 定義了任意屬性取 `string` 型別的值。

需要注意的是，**一旦定義了任意屬性，那麼確定屬性和可選屬性的型別都必須是它的型別的子集**：

```typescript
interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.
```

上例中，任意屬性的值允許是 `string`，但是可選屬性 `age` 的值卻是 `number`，`number` 不是 `string` 的子屬性，所以報錯了。

另外，在報錯資訊中可以看出，此時 `{ name: 'Tom', age: 25, gender: 'male' }` 的型別被推斷成了 `{ [x: string]: string | number; name: string; age: number; gender: string; }`，這是聯合型別和介面的結合。

## 唯讀屬性

有時候我們希望物件中的一些欄位只能在建立的時候被賦值，那麼可以用 `readonly` 定義唯讀屬性：

```typescript
interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
```

上例中，使用 `readonly` 定義的屬性 `id` 初始化後，又被賦值了，所以報錯了。

**注意：唯讀的約束存在於第一次給「物件」賦值的時候，而不是第一次給「唯讀屬性」賦值的時候**：

```typescript
interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

tom.id = 89757;

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
//   Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
```

上例中，報錯資訊有兩處，第一處是在對 `tom` 進行賦值的時候，沒有給 `id` 賦值。

第二處是在給 `tom.id` 賦值的時候，由於它是唯讀屬性，所以報錯了。

## 參考

* [Interfaces](http://www.typescriptlang.org/docs/handbook/interfaces.html)（[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Interfaces.html)）
* [上一章：聯合型別](/typescript-tutorial/basics/union-types.md)
* [下一章：陣列的型別](/typescript-tutorial/basics/type-of-array.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://willh.gitbook.io/typescript-tutorial/basics/type-of-object-interfaces.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
