在 JavaScript 中,我們群組和傳遞資料的基本方式是透過物件。在 TypeScript 中,我們透過物件型別來表示這些物件。
正如我們所見,它們可以是匿名的
tsTry
functiongreet (person : {name : string;age : number }) {return "Hello " +person .name ;}
或者可以使用介面或型別別名命名
tsTry
interfacePerson {name : string;age : number;}functiongreet (person :Person ) {return "Hello " +person .name ;}
tsTry
typePerson = {name : string;age : number;};functiongreet (person :Person ) {return "Hello " +person .name ;}
在以上三個範例中,我們撰寫的函式會使用包含屬性 name
(必須是 string
)和 age
(必須是 number
)的物件。
快速參考
如果您想快速瀏覽重要的日常語法,我們提供了 type
和 interface
的秘笈。
屬性修改器
物件型別中的每個屬性可以指定幾件事:型別、屬性是否為選用,以及屬性是否可以寫入。
選用屬性
很多時候,我們會發現自己處理的物件可能有一個屬性設定。在那些情況下,我們可以透過在屬性名稱的結尾加上問號 (?
) 來將這些屬性標記為選用。
tsTry
interfacePaintOptions {shape :Shape ;xPos ?: number;yPos ?: number;}functionpaintShape (opts :PaintOptions ) {// ...}constshape =getShape ();paintShape ({shape });paintShape ({shape ,xPos : 100 });paintShape ({shape ,yPos : 100 });paintShape ({shape ,xPos : 100,yPos : 100 });
在此範例中,xPos
和 yPos
都被視為選用。我們可以選擇提供其中任一個,因此上面對 paintShape
的每個呼叫都是有效的。選用性真正表示的是,如果屬性有設定,它最好有特定型別。
我們也可以從這些屬性讀取 - 但當我們在 strictNullChecks
下執行時,TypeScript 會告訴我們它們可能是 undefined
。
tsTry
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ;letyPos =opts .yPos ;// ...}
在 JavaScript 中,即使屬性從未設定,我們仍然可以存取它 - 它只會給我們 undefined
值。我們可以透過檢查來特別處理 undefined
。
tsTry
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ===undefined ? 0 :opts .xPos ;letyPos =opts .yPos ===undefined ? 0 :opts .yPos ;// ...}
請注意,這種為未指定值設定預設值的模式非常常見,以至於 JavaScript 有語法支援它。
tsTry
functionpaintShape ({shape ,xPos = 0,yPos = 0 }:PaintOptions ) {console .log ("x coordinate at",xPos );console .log ("y coordinate at",yPos );// ...}
在這裡,我們對 paintShape
的參數使用了 解構模式,並為 xPos
和 yPos
提供了 預設值。現在,xPos
和 yPos
都一定會出現在 paintShape
的主體中,但對於任何呼叫 paintShape
的呼叫者來說都是選用的。
請注意,目前沒有辦法在解構模式中放置型別註解。這是因為下列語法在 JavaScript 中已經有不同的意思。
tsTry
functiondraw ({shape :Shape ,xPos :number = 100 /*...*/ }) {Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?render (); shape Cannot find name 'xPos'.2304Cannot find name 'xPos'.render (); xPos }在物件解構模式中,
shape: Shape
表示「取得屬性shape
並在本地將它重新定義為一個名為Shape
的變數。同樣地,xPos: number
會建立一個名為number
的變數,其值根據參數的xPos
而定。
readonly
屬性
屬性也可以標記為 TypeScript 的 readonly
。雖然這不會在執行階段改變任何行為,但標記為 readonly
的屬性在類型檢查期間無法寫入。
tsTry
interfaceSomeType {readonlyprop : string;}functiondoSomething (obj :SomeType ) {// We can read from 'obj.prop'.console .log (`prop has the value '${obj .prop }'.`);// But we can't re-assign it.Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.obj .= "hello"; prop }
使用 readonly
修飾符並不一定表示值是完全不可變的,換句話說,它的內部內容無法被更改。這僅表示屬性本身無法被重新寫入。
tsTry
interfaceHome {readonlyresident : {name : string;age : number };}functionvisitForBirthday (home :Home ) {// We can read and update properties from 'home.resident'.console .log (`Happy birthday ${home .resident .name }!`);home .resident .age ++;}functionevict (home :Home ) {// But we can't write to the 'resident' property itself on a 'Home'.Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.home .= { resident name : "Victor the Evictor",age : 42,};}
管理 readonly
意味著什麼的預期非常重要。在開發時間使用 TypeScript 時,這有助於傳達物件應如何使用的意圖。在檢查這些類型是否相容時,TypeScript 沒有考慮兩個類型上的屬性是否為 readonly
,因此 readonly
屬性也可以透過別名進行更改。
tsTry
interfacePerson {name : string;age : number;}interfaceReadonlyPerson {readonlyname : string;readonlyage : number;}letwritablePerson :Person = {name : "Person McPersonface",age : 42,};// worksletreadonlyPerson :ReadonlyPerson =writablePerson ;console .log (readonlyPerson .age ); // prints '42'writablePerson .age ++;console .log (readonlyPerson .age ); // prints '43'
使用 對應修飾符,您可以移除 readonly
屬性。
索引簽章
有時您無法預先知道類型所有屬性的名稱,但您知道值的形狀。
在這些情況下,您可以使用索引簽章來描述可能值的類型,例如
tsTry
interfaceStringArray {[index : number]: string;}constmyArray :StringArray =getStringArray ();constsecondItem =myArray [1];
在上面,我們有一個具有索引簽章的 StringArray
介面。此索引簽章表示當 StringArray
使用 number
編製索引時,它將傳回 string
。
索引簽章屬性只允許某些類型:string
、number
、symbol
、範本字串模式,以及僅包含這些類型的聯合類型。
可以支援兩種索引器類型...
可以支援兩種索引器類型,但從數字索引器傳回的類型必須是從字串索引器傳回的類型的子類型。這是因為當使用 number
編製索引時,JavaScript 實際上會在編製物件索引之前將其轉換為 string
。這表示使用 100
(number
) 編製索引與使用 "100"
(string
) 編製索引相同,因此兩者需要一致。
tsTry
interfaceAnimal {name : string;}interfaceDog extendsAnimal {breed : string;}// Error: indexing with a numeric string might get you a completely separate type of Animal!interfaceNotOkay {['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 : number]:Animal ;[x : string]:Dog ;}
雖然字串索引簽章是描述「字典」模式的強大方式,但它們也會強制執行所有屬性與其回傳類型相符。這是因為字串索引宣告 obj.property
也可用作 obj["property"]
。在下列範例中,name
的類型與字串索引的類型不符,因此類型檢查器會傳回錯誤
tsTry
interfaceNumberDictionary {[index : string]: number;length : number; // okProperty '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'.: string; name }
不過,如果索引簽章是屬性類型的聯集,則不同類型的屬性是可以接受的
tsTry
interfaceNumberOrStringDictionary {[index : string]: number | string;length : number; // ok, length is a numbername : string; // ok, name is a string}
最後,你可以讓索引簽章為 readonly
,以防止指派給其索引
tsTry
interfaceReadonlyStringArray {readonly [index : number]: string;}letmyArray :ReadonlyStringArray =getReadOnlyStringArray ();Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.myArray [2] = "Mallory";
你無法設定 myArray[2]
,因為索引簽章為 readonly
。
過多屬性檢查
物件指派類型的位置和方式會影響類型系統。其中一個關鍵範例是過多屬性檢查,它會在建立物件時更徹底地驗證物件,並在建立時指派給物件類型。
tsTry
interfaceSquareConfig {color ?: string;width ?: number;}functioncreateSquare (config :SquareConfig ): {color : string;area : number } {return {color :config .color || "red",area :config .width ?config .width *config .width : 20,};}letArgument 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'?mySquare =createSquare ({colour : "red",width : 100 });
請注意傳遞給 createSquare
的引數拼寫為 colour
,而不是 color
。在純粹的 JavaScript 中,這類事情會靜默失敗。
你可以辯稱這個程式正確地進行了類型化,因為 width
屬性相容,沒有 color
屬性,而且額外的 colour
屬性並不重要。
不過,TypeScript 認為這段程式碼可能存在錯誤。物件文字會獲得特殊處理,並在指派給其他變數或傳遞為引數時進行 過多屬性檢查。如果物件文字有任何「目標類型」沒有的屬性,你會收到錯誤
tsTry
letArgument 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'?mySquare =createSquare ({colour : "red",width : 100 });
要解決這些檢查其實很簡單。最簡單的方法就是使用類型斷言
tsTry
letmySquare =createSquare ({width : 100,opacity : 0.5 } asSquareConfig );
不過,如果你確定物件可以擁有以特殊方式使用的額外屬性,更好的方法可能是新增字串索引簽章。如果 SquareConfig
可以擁有具有上述類型的 color
和 width
屬性,但 也可以 擁有任意數量的其他屬性,那麼我們可以這樣定義
tsTry
interfaceSquareConfig {color ?: string;width ?: number;[propName : string]: any;}
這裡我們表示 SquareConfig
可以擁有任意數量的屬性,只要它們不是 color
或 width
,它們的類型就不重要。
解決這些檢查的最後一種方法可能會有點令人驚訝,那就是將物件指派給另一個變數:由於指派 squareOptions
時不會進行過多屬性檢查,因此編譯器不會傳回錯誤
tsTry
letsquareOptions = {colour : "red",width : 100 };letmySquare =createSquare (squareOptions );
只要在 squareOptions
和 SquareConfig
之間有一個共用屬性,上述的解決方法就會有效。在此範例中,屬性為 width
。但如果變數沒有任何共用物件屬性,就會失敗。例如
tsTry
letsquareOptions = {colour : "red" };letType '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.mySquare =createSquare (); squareOptions
請記住,對於上述這類簡單的程式碼,您可能不應該嘗試「迴避」這些檢查。對於具有方法和狀態的更複雜物件文字,您可能需要記住這些技巧,但大部分的額外屬性錯誤實際上都是錯誤。
這表示,如果您遇到選項袋之類的額外屬性檢查問題,您可能需要修改一些類型宣告。在此情況下,如果將具有 color
或 colour
屬性的物件傳遞給 createSquare
是可以的,您應該修正 SquareConfig
的定義以反映這一點。
擴充類型
具有可能比其他類型更具體版本的類型相當常見。例如,我們可能有 BasicAddress
類型,用來描述在美國寄信和包裹所需的欄位。
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}
在某些情況下,這就足夠了,但如果地址的建築物有多個單位,地址通常會有一個與之關聯的單位編號。然後,我們可以描述 AddressWithUnit
。
tsTry
interfaceAddressWithUnit {name ?: string;unit : string;street : string;city : string;country : string;postalCode : string;}
這可以完成工作,但缺點是當我們的變更純粹是新增時,我們必須重複 BasicAddress
中的所有其他欄位。相反地,我們可以擴充原始的 BasicAddress
類型,並只新增 AddressWithUnit
中獨有的新欄位。
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}interfaceAddressWithUnit extendsBasicAddress {unit : string;}
interface
上的 extends
關鍵字讓我們可以有效地從其他已命名類型複製成員,並新增任何我們想要的成員。這對於減少我們必須撰寫的類型宣告樣板,以及傳達意圖,即同一屬性的幾個不同宣告可能相關,會很有用。例如,AddressWithUnit
不需要重複 street
屬性,而且因為 street
來自 BasicAddress
,讀者會知道這兩個類型在某種程度上是相關的。
interface
也能從多個類型延伸。
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}interfaceColorfulCircle extendsColorful ,Circle {}constcc :ColorfulCircle = {color : "red",radius : 42,};
交集類型
interface
讓我們可以透過延伸其他類型來建構新的類型。TypeScript 提供了另一種稱為交集類型的建構,主要用於結合現有的物件類型。
交集類型使用 &
算子定義。
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}typeColorfulCircle =Colorful &Circle ;
在這裡,我們交集了 Colorful
和 Circle
以產生一個新類型,它具有 Colorful
和 Circle
的所有成員。
tsTry
functiondraw (circle :Colorful &Circle ) {console .log (`Color was ${circle .color }`);console .log (`Radius was ${circle .radius }`);}// okaydraw ({color : "blue",radius : 42 });// oopsArgument 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'?draw ({color : "red",raidus : 42 });
介面與交集
我們剛剛看了兩種結合類型的方式,它們很相似,但實際上有些微不同。對於介面,我們可以使用 extends
子句從其他類型延伸,而且我們可以對交集執行類似的操作,並使用類型別名命名結果。這兩者之間的主要差異在於如何處理衝突,而這種差異通常是你在介面和交集類型的類型別名之間選擇一個的主要原因之一。
泛型物件類型
讓我們想像一個可以包含任何值的 Box
類型 - 字串
、數字
、長頸鹿
,隨便什麼。
tsTry
interfaceBox {contents : any;}
現在,contents
屬性被類型化為 any
,這很管用,但可能會導致後續意外。
我們可以改用 unknown
,但這表示在我們已經知道 contents
類型的案例中,我們需要執行預防性檢查,或使用容易出錯的類型斷言。
tsTry
interfaceBox {contents : unknown;}letx :Box = {contents : "hello world",};// we could check 'x.contents'if (typeofx .contents === "string") {console .log (x .contents .toLowerCase ());}// or we could use a type assertionconsole .log ((x .contents as string).toLowerCase ());
一種類型安全的做法是為每種 contents
類型建立不同的 Box
類型。
tsTry
interfaceNumberBox {contents : number;}interfaceStringBox {contents : string;}interfaceBooleanBox {contents : boolean;}
但這表示我們必須建立不同的函式,或函式的重載,才能在這些類型上運作。
tsTry
functionsetContents (box :StringBox ,newContents : string): void;functionsetContents (box :NumberBox ,newContents : number): void;functionsetContents (box :BooleanBox ,newContents : boolean): void;functionsetContents (box : {contents : any },newContents : any) {box .contents =newContents ;}
這會產生很多樣板程式碼。此外,我們可能稍後需要引入新的類型和重載。這很令人沮喪,因為我們的方塊類型和重載實際上都是相同的。
相反地,我們可以建立一個宣告 類型參數 的泛型 Box
類型。
tsTry
interfaceBox <Type > {contents :Type ;}
你可以這樣理解:「Type
的 Box
是 contents
具有 Type
類型的東西」。稍後,當我們參照 Box
時,我們必須提供一個類型引數來取代 Type
。
tsTry
letbox :Box <string>;
將 Box
視為真實類型的範本,其中 Type
是將被其他類型取代的佔位符。當 TypeScript 看見 Box<string>
時,它會將 Box<Type>
中的每個 Type
執行例都替換為 字串
,並最終處理類似 { contents: string }
的東西。換句話說,Box<string>
和我們先前的 StringBox
運作方式相同。
tsTry
interfaceBox <Type > {contents :Type ;}interfaceStringBox {contents : string;}letboxA :Box <string> = {contents : "hello" };boxA .contents ;letboxB :StringBox = {contents : "world" };boxB .contents ;
Box
可重複使用,因為 Type
可以用任何東西取代。這表示當我們需要一個新類型的方塊時,我們根本不需要宣告新的 Box
類型(儘管我們當然可以這麼做,如果我們願意)。
tsTry
interfaceBox <Type > {contents :Type ;}interfaceApple {// ....}// Same as '{ contents: Apple }'.typeAppleBox =Box <Apple >;
這也表示我們可以完全避免重載,改用 泛型函式。
tsTry
functionsetContents <Type >(box :Box <Type >,newContents :Type ) {box .contents =newContents ;}
值得注意的是,類型別名也可以是泛型的。我們可以定義我們的 Box<Type>
新介面,如下所示
tsTry
interfaceBox <Type > {contents :Type ;}
改用類型別名
tsTry
typeBox <Type > = {contents :Type ;};
由於類型別名(與介面不同)可以描述的不只是物件類型,我們也可以使用它們來撰寫其他類型的泛型輔助類型。
tsTry
typeOrNull <Type > =Type | null;typeOneOrMany <Type > =Type |Type [];typeOneOrManyOrNull <Type > =OrNull <OneOrMany <Type >>;typeOneOrManyOrNullStrings =OneOrManyOrNull <string>;
我們稍後會再回到類型別名。
陣列
類型
泛型物件類型通常是某種容器類型,其運作方式與其所包含元素的類型無關。資料結構以這種方式運作非常理想,因為它們可以在不同的資料類型中重複使用。
事實證明,我們在整個手冊中一直在使用這種類型:Array
類型。每當我們寫出像 number[]
或 string[]
這樣的類型時,這實際上只是 Array<number>
和 Array<string>
的簡寫。
tsTry
functiondoSomething (value :Array <string>) {// ...}letmyArray : string[] = ["hello", "world"];// either of these work!doSomething (myArray );doSomething (newArray ("hello", "world"));
與上面的 Box
類型非常相似,Array
本身也是一種泛型類型。
tsTry
interfaceArray <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;// ...}
現代 JavaScript 也提供了其他泛型的資料結構,例如 Map<K, V>
、Set<T>
和 Promise<T>
。這一切都意味著,由於 Map
、Set
和 Promise
的行為方式,它們可以與任何類型的集合一起使用。
ReadonlyArray
類型
ReadonlyArray
是一種特殊類型,用於描述不應變更的陣列。
tsTry
functiondoStuff (values :ReadonlyArray <string>) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
與屬性的 readonly
修飾詞非常相似,它主要是一種我們可用於意圖的工具。當我們看到傳回 ReadonlyArray
的函式時,它告訴我們我們不應該變更任何內容,而當我們看到使用 ReadonlyArray
的函式時,它告訴我們,我們可以將任何陣列傳遞給該函式,而不用擔心它會變更其內容。
與 Array
不同,沒有我們可以使用的 ReadonlyArray
建構函式。
tsTry
new'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.("red", "green", "blue"); ReadonlyArray
相反,我們可以將常規 Array
指定給 ReadonlyArray
。
tsTry
constroArray :ReadonlyArray <string> = ["red", "green", "blue"];
正如 TypeScript 為 Array<Type>
提供了 Type[]
的簡寫語法一樣,它也為 ReadonlyArray<Type>
提供了 readonly Type[]
的簡寫語法。
tsTry
functiondoStuff (values : readonly string[]) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
最後要注意的一點是,與 readonly
屬性修飾詞不同,常規 Array
和 ReadonlyArray
之間的可指派性不是雙向的。
tsTry
letx : readonly string[] = [];lety : string[] = [];x =y ;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[]'.= y x ;
元組類型
元組類型是另一種陣列
類型,它確切地知道它包含多少個元素,以及它在特定位置包含哪些類型。
tsTry
typeStringNumberPair = [string, number];
在這裡,StringNumberPair
是字串
和數字
的元組類型。像ReadonlyArray
一樣,它在執行階段沒有表示,但對 TypeScript 來說很重要。對於類型系統,StringNumberPair
描述了其0
索引包含字串
且其1
索引包含數字
的陣列。
tsTry
functiondoSomething (pair : [string, number]) {consta =pair [0];constb =pair [1];// ...}doSomething (["hello", 42]);
如果我們嘗試索引超過元素的數量,我們將會收到錯誤。
tsTry
functiondoSomething (pair : [string, number]) {// ...constTuple 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'.c =pair [2 ];}
我們也可以使用 JavaScript 的陣列解構來解構元組。
tsTry
functiondoSomething (stringHash : [string, number]) {const [inputString ,hash ] =stringHash ;console .log (inputString );console .log (hash );}
元組類型在高度依賴慣例的 API 中很有用,其中每個元素的含義都是「顯而易見的」。這讓我們在解構它們時可以靈活地命名我們的變數。在上面的範例中,我們可以將元素
0
和1
命名為我們想要的任何名稱。然而,由於並非每個使用者都持有相同的觀點,因此值得重新考慮是否使用具有描述性屬性名稱的物件可能更適合您的 API。
除了這些長度檢查之外,像這樣的簡單元組類型等同於宣告特定索引的屬性並宣告length
具有數字文字類型的陣列
版本。
tsTry
interfaceStringNumberPair {// specialized propertieslength : 2;0: string;1: number;// Other 'Array<string | number>' members...slice (start ?: number,end ?: number):Array <string | number>;}
您可能感興趣的另一件事是,元組可以透過在元素類型後面寫上問號 (?
) 來擁有可選屬性。可選元組元素只能出現在最後,也會影響length
的類型。
tsTry
typeEither2dOr3d = [number, number, number?];functionsetCoordinate (coord :Either2dOr3d ) {const [x ,y ,z ] =coord ;console .log (`Provided coordinates had ${coord .length } dimensions`);}
元組也可以有剩餘元素,它們必須是陣列/元組類型。
tsTry
typeStringNumberBooleans = [string, number, ...boolean[]];typeStringBooleansNumber = [string, ...boolean[], number];typeBooleansStringNumber = [...boolean[], string, number];
StringNumberBooleans
描述了一個元組,其前兩個元素分別是字串
和數字
,但它可能有任意數量的布林
元素。StringBooleansNumber
描述了一個元組,其第一個元素是字串
,然後是任意數量的布林
元素,最後以數字
結尾。BooleansStringNumber
描述一個元組,它的起始元素是任意數量的boolean
,並以string
和number
結尾。
具有 rest 元素的元組沒有設定的「長度」- 它只在不同位置有一組已知的元素。
tsTry
consta :StringNumberBooleans = ["hello", 1];constb :StringNumberBooleans = ["beautiful", 2, true];constc :StringNumberBooleans = ["world", 3, true, false, true, false, true];
為什麼可選和 rest 元素可能有用?好吧,它允許 TypeScript 將元組與參數清單對應。元組類型可以用於 rest 參數和參數,因此以下
tsTry
functionreadButtonInput (...args : [string, number, ...boolean[]]) {const [name ,version , ...input ] =args ;// ...}
基本上等於
tsTry
functionreadButtonInput (name : string,version : number, ...input : boolean[]) {// ...}
當您想使用 rest 參數取得可變數量的參數,並且需要最少數量的元素時,這很方便,但您不想引入中間變數。
readonly
元組類型
關於元組類型的最後一個註解 - 元組類型有 readonly
變體,並且可以透過在它們前面加上 readonly
修飾詞來指定 - 就像陣列簡寫語法一樣。
tsTry
functiondoSomething (pair : readonly [string, number]) {// ...}
正如您所預期的,在 TypeScript 中不允許寫入 readonly
元組的任何屬性。
tsTry
functiondoSomething (pair : readonly [string, number]) {Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.pair [0 ] = "hello!";}
在大部分程式碼中,元組傾向於建立並保持不修改,因此盡可能將類型註解為 readonly
元組是一個好的預設值。這也很重要,因為具有 const
斷言的陣列文字將會以 readonly
元組類型推斷出來。
tsTry
letpoint = [3, 4] asconst ;functiondistanceFromOrigin ([x ,y ]: [number, number]) {returnMath .sqrt (x ** 2 +y ** 2);}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]'.distanceFromOrigin (); point
在此,distanceFromOrigin
從不修改其元素,但預期一個可變元組。由於 point
的類型被推斷為 readonly [3, 4]
,因此它與 [number, number]
不相容,因為該類型無法保證 point
的元素不會被變異。