物件類型

在 JavaScript 中,我們群組和傳遞資料的基本方式是透過物件。在 TypeScript 中,我們透過物件型別來表示這些物件。

正如我們所見,它們可以是匿名的

ts
function greet(person: { name: string; age: number }) {
return "Hello " + person.name;
}
Try

或者可以使用介面或型別別名命名

ts
interface Person {
name: string;
age: number;
}
 
function greet(person: Person) {
return "Hello " + person.name;
}
Try

ts
type Person = {
name: string;
age: number;
};
 
function greet(person: Person) {
return "Hello " + person.name;
}
Try

在以上三個範例中,我們撰寫的函式會使用包含屬性 name(必須是 string)和 age(必須是 number)的物件。

快速參考

如果您想快速瀏覽重要的日常語法,我們提供了 typeinterface 的秘笈。

屬性修改器

物件型別中的每個屬性可以指定幾件事:型別、屬性是否為選用,以及屬性是否可以寫入。

選用屬性

很多時候,我們會發現自己處理的物件可能有一個屬性設定。在那些情況下,我們可以透過在屬性名稱的結尾加上問號 (?) 來將這些屬性標記為選用

ts
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
 
function paintShape(opts: PaintOptions) {
// ...
}
 
const shape = getShape();
paintShape({ shape });
paintShape({ shape, xPos: 100 });
paintShape({ shape, yPos: 100 });
paintShape({ shape, xPos: 100, yPos: 100 });
Try

在此範例中,xPosyPos 都被視為選用。我們可以選擇提供其中任一個,因此上面對 paintShape 的每個呼叫都是有效的。選用性真正表示的是,如果屬性設定,它最好有特定型別。

我們也可以從這些屬性讀取 - 但當我們在 strictNullChecks 下執行時,TypeScript 會告訴我們它們可能是 undefined

ts
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos;
(property) PaintOptions.xPos?: number | undefined
let yPos = opts.yPos;
(property) PaintOptions.yPos?: number | undefined
// ...
}
Try

在 JavaScript 中,即使屬性從未設定,我們仍然可以存取它 - 它只會給我們 undefined 值。我們可以透過檢查來特別處理 undefined

ts
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos === undefined ? 0 : opts.xPos;
let xPos: number
let yPos = opts.yPos === undefined ? 0 : opts.yPos;
let yPos: number
// ...
}
Try

請注意,這種為未指定值設定預設值的模式非常常見,以至於 JavaScript 有語法支援它。

ts
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos);
(parameter) xPos: number
console.log("y coordinate at", yPos);
(parameter) yPos: number
// ...
}
Try

在這裡,我們對 paintShape 的參數使用了 解構模式,並為 xPosyPos 提供了 預設值。現在,xPosyPos 都一定會出現在 paintShape 的主體中,但對於任何呼叫 paintShape 的呼叫者來說都是選用的。

請注意,目前沒有辦法在解構模式中放置型別註解。這是因為下列語法在 JavaScript 中已經有不同的意思。

ts
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape);
Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?
render(xPos);
Cannot find name 'xPos'.2304Cannot find name 'xPos'.
}
Try

在物件解構模式中,shape: Shape 表示「取得屬性 shape 並在本地將它重新定義為一個名為 Shape 的變數。同樣地,xPos: number 會建立一個名為 number 的變數,其值根據參數的 xPos 而定。

readonly 屬性

屬性也可以標記為 TypeScript 的 readonly。雖然這不會在執行階段改變任何行為,但標記為 readonly 的屬性在類型檢查期間無法寫入。

ts
interface SomeType {
readonly prop: string;
}
 
function doSomething(obj: SomeType) {
// We can read from 'obj.prop'.
console.log(`prop has the value '${obj.prop}'.`);
 
// But we can't re-assign it.
obj.prop = "hello";
Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.
}
Try

使用 readonly 修飾符並不一定表示值是完全不可變的,換句話說,它的內部內容無法被更改。這僅表示屬性本身無法被重新寫入。

ts
interface Home {
readonly resident: { name: string; age: number };
}
 
function visitForBirthday(home: Home) {
// We can read and update properties from 'home.resident'.
console.log(`Happy birthday ${home.resident.name}!`);
home.resident.age++;
}
 
function evict(home: Home) {
// But we can't write to the 'resident' property itself on a 'Home'.
home.resident = {
Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.
name: "Victor the Evictor",
age: 42,
};
}
Try

管理 readonly 意味著什麼的預期非常重要。在開發時間使用 TypeScript 時,這有助於傳達物件應如何使用的意圖。在檢查這些類型是否相容時,TypeScript 沒有考慮兩個類型上的屬性是否為 readonly,因此 readonly 屬性也可以透過別名進行更改。

ts
interface Person {
name: string;
age: number;
}
 
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
 
let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};
 
// works
let readonlyPerson: ReadonlyPerson = writablePerson;
 
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'
Try

使用 對應修飾符,您可以移除 readonly 屬性。

索引簽章

有時您無法預先知道類型所有屬性的名稱,但您知道值的形狀。

在這些情況下,您可以使用索引簽章來描述可能值的類型,例如

ts
interface StringArray {
[index: number]: string;
}
 
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
const secondItem: string
Try

在上面,我們有一個具有索引簽章的 StringArray 介面。此索引簽章表示當 StringArray 使用 number 編製索引時,它將傳回 string

索引簽章屬性只允許某些類型:stringnumbersymbol、範本字串模式,以及僅包含這些類型的聯合類型。

可以支援兩種索引器類型...

可以支援兩種索引器類型,但從數字索引器傳回的類型必須是從字串索引器傳回的類型的子類型。這是因為當使用 number 編製索引時,JavaScript 實際上會在編製物件索引之前將其轉換為 string。這表示使用 100 (number) 編製索引與使用 "100" (string) 編製索引相同,因此兩者需要一致。

ts
interface Animal {
name: string;
}
 
interface Dog extends Animal {
breed: string;
}
 
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
[x: number]: Animal;
'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.2413'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.
[x: string]: Dog;
}
Try

雖然字串索引簽章是描述「字典」模式的強大方式,但它們也會強制執行所有屬性與其回傳類型相符。這是因為字串索引宣告 obj.property 也可用作 obj["property"]。在下列範例中,name 的類型與字串索引的類型不符,因此類型檢查器會傳回錯誤

ts
interface NumberDictionary {
[index: string]: number;
 
length: number; // ok
name: string;
Property 'name' of type 'string' is not assignable to 'string' index type 'number'.2411Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
}
Try

不過,如果索引簽章是屬性類型的聯集,則不同類型的屬性是可以接受的

ts
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
Try

最後,你可以讓索引簽章為 readonly,以防止指派給其索引

ts
interface ReadonlyStringArray {
readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory";
Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.
Try

你無法設定 myArray[2],因為索引簽章為 readonly

過多屬性檢查

物件指派類型的位置和方式會影響類型系統。其中一個關鍵範例是過多屬性檢查,它會在建立物件時更徹底地驗證物件,並在建立時指派給物件類型。

ts
interface SquareConfig {
color?: string;
width?: number;
}
 
function createSquare(config: SquareConfig): { color: string; area: number } {
return {
color: config.color || "red",
area: config.width ? config.width * config.width : 20,
};
}
 
let mySquare = createSquare({ colour: "red", width: 100 });
Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'. Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2345Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'. Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
Try

請注意傳遞給 createSquare 的引數拼寫為 colour,而不是 color。在純粹的 JavaScript 中,這類事情會靜默失敗。

你可以辯稱這個程式正確地進行了類型化,因為 width 屬性相容,沒有 color 屬性,而且額外的 colour 屬性並不重要。

不過,TypeScript 認為這段程式碼可能存在錯誤。物件文字會獲得特殊處理,並在指派給其他變數或傳遞為引數時進行 過多屬性檢查。如果物件文字有任何「目標類型」沒有的屬性,你會收到錯誤

ts
let mySquare = createSquare({ colour: "red", width: 100 });
Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'. Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2345Argument of type '{ colour: string; width: number; }' is not assignable to parameter of type 'SquareConfig'. Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?
Try

要解決這些檢查其實很簡單。最簡單的方法就是使用類型斷言

ts
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
Try

不過,如果你確定物件可以擁有以特殊方式使用的額外屬性,更好的方法可能是新增字串索引簽章。如果 SquareConfig 可以擁有具有上述類型的 colorwidth 屬性,但 也可以 擁有任意數量的其他屬性,那麼我們可以這樣定義

ts
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
Try

這裡我們表示 SquareConfig 可以擁有任意數量的屬性,只要它們不是 colorwidth,它們的類型就不重要。

解決這些檢查的最後一種方法可能會有點令人驚訝,那就是將物件指派給另一個變數:由於指派 squareOptions 時不會進行過多屬性檢查,因此編譯器不會傳回錯誤

ts
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
Try

只要在 squareOptionsSquareConfig 之間有一個共用屬性,上述的解決方法就會有效。在此範例中,屬性為 width。但如果變數沒有任何共用物件屬性,就會失敗。例如

ts
let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);
Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.
Try

請記住,對於上述這類簡單的程式碼,您可能不應該嘗試「迴避」這些檢查。對於具有方法和狀態的更複雜物件文字,您可能需要記住這些技巧,但大部分的額外屬性錯誤實際上都是錯誤。

這表示,如果您遇到選項袋之類的額外屬性檢查問題,您可能需要修改一些類型宣告。在此情況下,如果將具有 colorcolour 屬性的物件傳遞給 createSquare 是可以的,您應該修正 SquareConfig 的定義以反映這一點。

擴充類型

具有可能比其他類型更具體版本的類型相當常見。例如,我們可能有 BasicAddress 類型,用來描述在美國寄信和包裹所需的欄位。

ts
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
Try

在某些情況下,這就足夠了,但如果地址的建築物有多個單位,地址通常會有一個與之關聯的單位編號。然後,我們可以描述 AddressWithUnit

ts
interface AddressWithUnit {
name?: string;
unit: string;
street: string;
city: string;
country: string;
postalCode: string;
}
Try

這可以完成工作,但缺點是當我們的變更純粹是新增時,我們必須重複 BasicAddress 中的所有其他欄位。相反地,我們可以擴充原始的 BasicAddress 類型,並只新增 AddressWithUnit 中獨有的新欄位。

ts
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
 
interface AddressWithUnit extends BasicAddress {
unit: string;
}
Try

interface 上的 extends 關鍵字讓我們可以有效地從其他已命名類型複製成員,並新增任何我們想要的成員。這對於減少我們必須撰寫的類型宣告樣板,以及傳達意圖,即同一屬性的幾個不同宣告可能相關,會很有用。例如,AddressWithUnit 不需要重複 street 屬性,而且因為 street 來自 BasicAddress,讀者會知道這兩個類型在某種程度上是相關的。

interface 也能從多個類型延伸。

ts
interface Colorful {
color: string;
}
 
interface Circle {
radius: number;
}
 
interface ColorfulCircle extends Colorful, Circle {}
 
const cc: ColorfulCircle = {
color: "red",
radius: 42,
};
Try

交集類型

interface 讓我們可以透過延伸其他類型來建構新的類型。TypeScript 提供了另一種稱為交集類型的建構,主要用於結合現有的物件類型。

交集類型使用 & 算子定義。

ts
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
 
type ColorfulCircle = Colorful & Circle;
Try

在這裡,我們交集了 ColorfulCircle 以產生一個新類型,它具有 Colorful Circle 的所有成員。

ts
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
 
// okay
draw({ color: "blue", radius: 42 });
 
// oops
draw({ color: "red", raidus: 42 });
Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'. Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?2345Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'. Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?
Try

介面與交集

我們剛剛看了兩種結合類型的方式,它們很相似,但實際上有些微不同。對於介面,我們可以使用 extends 子句從其他類型延伸,而且我們可以對交集執行類似的操作,並使用類型別名命名結果。這兩者之間的主要差異在於如何處理衝突,而這種差異通常是你在介面和交集類型的類型別名之間選擇一個的主要原因之一。

泛型物件類型

讓我們想像一個可以包含任何值的 Box 類型 - 字串數字長頸鹿,隨便什麼。

ts
interface Box {
contents: any;
}
Try

現在,contents 屬性被類型化為 any,這很管用,但可能會導致後續意外。

我們可以改用 unknown,但這表示在我們已經知道 contents 類型的案例中,我們需要執行預防性檢查,或使用容易出錯的類型斷言。

ts
interface Box {
contents: unknown;
}
 
let x: Box = {
contents: "hello world",
};
 
// we could check 'x.contents'
if (typeof x.contents === "string") {
console.log(x.contents.toLowerCase());
}
 
// or we could use a type assertion
console.log((x.contents as string).toLowerCase());
Try

一種類型安全的做法是為每種 contents 類型建立不同的 Box 類型。

ts
interface NumberBox {
contents: number;
}
 
interface StringBox {
contents: string;
}
 
interface BooleanBox {
contents: boolean;
}
Try

但這表示我們必須建立不同的函式,或函式的重載,才能在這些類型上運作。

ts
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}
Try

這會產生很多樣板程式碼。此外,我們可能稍後需要引入新的類型和重載。這很令人沮喪,因為我們的方塊類型和重載實際上都是相同的。

相反地,我們可以建立一個宣告 類型參數泛型 Box 類型。

ts
interface Box<Type> {
contents: Type;
}
Try

你可以這樣理解:「TypeBoxcontents 具有 Type 類型的東西」。稍後,當我們參照 Box 時,我們必須提供一個類型引數來取代 Type

ts
let box: Box<string>;
Try

Box 視為真實類型的範本,其中 Type 是將被其他類型取代的佔位符。當 TypeScript 看見 Box<string> 時,它會將 Box<Type> 中的每個 Type 執行例都替換為 字串,並最終處理類似 { contents: string } 的東西。換句話說,Box<string> 和我們先前的 StringBox 運作方式相同。

ts
interface Box<Type> {
contents: Type;
}
interface StringBox {
contents: string;
}
 
let boxA: Box<string> = { contents: "hello" };
boxA.contents;
(property) Box<string>.contents: string
 
let boxB: StringBox = { contents: "world" };
boxB.contents;
(property) StringBox.contents: string
Try

Box 可重複使用,因為 Type 可以用任何東西取代。這表示當我們需要一個新類型的方塊時,我們根本不需要宣告新的 Box 類型(儘管我們當然可以這麼做,如果我們願意)。

ts
interface Box<Type> {
contents: Type;
}
 
interface Apple {
// ....
}
 
// Same as '{ contents: Apple }'.
type AppleBox = Box<Apple>;
Try

這也表示我們可以完全避免重載,改用 泛型函式

ts
function setContents<Type>(box: Box<Type>, newContents: Type) {
box.contents = newContents;
}
Try

值得注意的是,類型別名也可以是泛型的。我們可以定義我們的 Box<Type> 新介面,如下所示

ts
interface Box<Type> {
contents: Type;
}
Try

改用類型別名

ts
type Box<Type> = {
contents: Type;
};
Try

由於類型別名(與介面不同)可以描述的不只是物件類型,我們也可以使用它們來撰寫其他類型的泛型輔助類型。

ts
type OrNull<Type> = Type | null;
 
type OneOrMany<Type> = Type | Type[];
 
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;
type OneOrManyOrNull<Type> = OneOrMany<Type> | null
 
type OneOrManyOrNullStrings = OneOrManyOrNull<string>;
type OneOrManyOrNullStrings = OneOrMany<string> | null
Try

我們稍後會再回到類型別名。

陣列類型

泛型物件類型通常是某種容器類型,其運作方式與其所包含元素的類型無關。資料結構以這種方式運作非常理想,因為它們可以在不同的資料類型中重複使用。

事實證明,我們在整個手冊中一直在使用這種類型:Array 類型。每當我們寫出像 number[]string[] 這樣的類型時,這實際上只是 Array<number>Array<string> 的簡寫。

ts
function doSomething(value: Array<string>) {
// ...
}
 
let myArray: string[] = ["hello", "world"];
 
// either of these work!
doSomething(myArray);
doSomething(new Array("hello", "world"));
Try

與上面的 Box 類型非常相似,Array 本身也是一種泛型類型。

ts
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;
 
/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;
 
/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;
 
// ...
}
Try

現代 JavaScript 也提供了其他泛型的資料結構,例如 Map<K, V>Set<T>Promise<T>。這一切都意味著,由於 MapSetPromise 的行為方式,它們可以與任何類型的集合一起使用。

ReadonlyArray 類型

ReadonlyArray 是一種特殊類型,用於描述不應變更的陣列。

ts
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

與屬性的 readonly 修飾詞非常相似,它主要是一種我們可用於意圖的工具。當我們看到傳回 ReadonlyArray 的函式時,它告訴我們我們不應該變更任何內容,而當我們看到使用 ReadonlyArray 的函式時,它告訴我們,我們可以將任何陣列傳遞給該函式,而不用擔心它會變更其內容。

Array 不同,沒有我們可以使用的 ReadonlyArray 建構函式。

ts
new ReadonlyArray("red", "green", "blue");
'ReadonlyArray' only refers to a type, but is being used as a value here.2693'ReadonlyArray' only refers to a type, but is being used as a value here.
Try

相反,我們可以將常規 Array 指定給 ReadonlyArray

ts
const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
Try

正如 TypeScript 為 Array<Type> 提供了 Type[] 的簡寫語法一樣,它也為 ReadonlyArray<Type> 提供了 readonly Type[] 的簡寫語法。

ts
function doStuff(values: readonly string[]) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
 
// ...but we can't mutate 'values'.
values.push("hello!");
Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.
}
Try

最後要注意的一點是,與 readonly 屬性修飾詞不同,常規 ArrayReadonlyArray 之間的可指派性不是雙向的。

ts
let x: readonly string[] = [];
let y: string[] = [];
 
x = y;
y = x;
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.4104The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
Try

元組類型

元組類型是另一種陣列類型,它確切地知道它包含多少個元素,以及它在特定位置包含哪些類型。

ts
type StringNumberPair = [string, number];
Try

在這裡,StringNumberPair字串數字的元組類型。像ReadonlyArray一樣,它在執行階段沒有表示,但對 TypeScript 來說很重要。對於類型系統,StringNumberPair描述了其0索引包含字串且其1索引包含數字的陣列。

ts
function doSomething(pair: [string, number]) {
const a = pair[0];
const a: string
const b = pair[1];
const b: number
// ...
}
 
doSomething(["hello", 42]);
Try

如果我們嘗試索引超過元素的數量,我們將會收到錯誤。

ts
function doSomething(pair: [string, number]) {
// ...
 
const c = pair[2];
Tuple type '[string, number]' of length '2' has no element at index '2'.2493Tuple type '[string, number]' of length '2' has no element at index '2'.
}
Try

我們也可以使用 JavaScript 的陣列解構來解構元組

ts
function doSomething(stringHash: [string, number]) {
const [inputString, hash] = stringHash;
 
console.log(inputString);
const inputString: string
 
console.log(hash);
const hash: number
}
Try

元組類型在高度依賴慣例的 API 中很有用,其中每個元素的含義都是「顯而易見的」。這讓我們在解構它們時可以靈活地命名我們的變數。在上面的範例中,我們可以將元素01命名為我們想要的任何名稱。

然而,由於並非每個使用者都持有相同的觀點,因此值得重新考慮是否使用具有描述性屬性名稱的物件可能更適合您的 API。

除了這些長度檢查之外,像這樣的簡單元組類型等同於宣告特定索引的屬性並宣告length具有數字文字類型的陣列版本。

ts
interface StringNumberPair {
// specialized properties
length: 2;
0: string;
1: number;
 
// Other 'Array<string | number>' members...
slice(start?: number, end?: number): Array<string | number>;
}
Try

您可能感興趣的另一件事是,元組可以透過在元素類型後面寫上問號 (?) 來擁有可選屬性。可選元組元素只能出現在最後,也會影響length的類型。

ts
type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
const [x, y, z] = coord;
const z: number | undefined
 
console.log(`Provided coordinates had ${coord.length} dimensions`);
(property) length: 2 | 3
}
Try

元組也可以有剩餘元素,它們必須是陣列/元組類型。

ts
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];
Try
  • StringNumberBooleans描述了一個元組,其前兩個元素分別是字串數字,但它可能有任意數量的布林元素。
  • StringBooleansNumber描述了一個元組,其第一個元素是字串,然後是任意數量的布林元素,最後以數字結尾。
  • BooleansStringNumber 描述一個元組,它的起始元素是任意數量的 boolean,並以 stringnumber 結尾。

具有 rest 元素的元組沒有設定的「長度」- 它只在不同位置有一組已知的元素。

ts
const a: StringNumberBooleans = ["hello", 1];
const b: StringNumberBooleans = ["beautiful", 2, true];
const c: StringNumberBooleans = ["world", 3, true, false, true, false, true];
Try

為什麼可選和 rest 元素可能有用?好吧,它允許 TypeScript 將元組與參數清單對應。元組類型可以用於 rest 參數和參數,因此以下

ts
function readButtonInput(...args: [string, number, ...boolean[]]) {
const [name, version, ...input] = args;
// ...
}
Try

基本上等於

ts
function readButtonInput(name: string, version: number, ...input: boolean[]) {
// ...
}
Try

當您想使用 rest 參數取得可變數量的參數,並且需要最少數量的元素時,這很方便,但您不想引入中間變數。

readonly 元組類型

關於元組類型的最後一個註解 - 元組類型有 readonly 變體,並且可以透過在它們前面加上 readonly 修飾詞來指定 - 就像陣列簡寫語法一樣。

ts
function doSomething(pair: readonly [string, number]) {
// ...
}
Try

正如您所預期的,在 TypeScript 中不允許寫入 readonly 元組的任何屬性。

ts
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!";
Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.
}
Try

在大部分程式碼中,元組傾向於建立並保持不修改,因此盡可能將類型註解為 readonly 元組是一個好的預設值。這也很重要,因為具有 const 斷言的陣列文字將會以 readonly 元組類型推斷出來。

ts
let point = [3, 4] as const;
 
function distanceFromOrigin([x, y]: [number, number]) {
return Math.sqrt(x ** 2 + y ** 2);
}
 
distanceFromOrigin(point);
Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.2345Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.
Try

在此,distanceFromOrigin 從不修改其元素,但預期一個可變元組。由於 point 的類型被推斷為 readonly [3, 4],因此它與 [number, number] 不相容,因為該類型無法保證 point 的元素不會被變異。

TypeScript 文件是一個開放原始碼專案。協助我們改善這些頁面 傳送 Pull Request

此頁面的貢獻者
DRDaniel Rosenwasser (52)
OTOrta Therox (16)
338elements  (2)
BRBruce Robertson (2)
ARAlan Rempel (2)
18+

最後更新:2024 年 3 月 21 日