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 ;}
上記の3つの例ではすべて、`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`は、値がパラメータの`xPos`に基づく`number`という名前の変数を作成します。
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は、2つの型のプロパティが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
、テンプレート文字列パターン、およびこれらの型のみで構成されるユニオン型などの一部の型のみが許可されます。
両方のタイプのインデクサをサポートすることは可能です...
両方のタイプのインデクサをサポートすることは可能ですが、数値インデクサから返される型は、文字列インデクサから返される型のサブタイプである必要があります。これは、JavaScriptが数値でインデックス付けを行う場合、実際にはオブジェクトにインデックス付けする前にそれを文字列に変換するためです。つまり、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";
インデックスシグネチャがreadonly
なので、myArray[2]
を設定することはできません。
超過プロパティチェック
オブジェクトに型が割り当てられる場所と方法は、型システムに影響を与える可能性があります。その重要な例の一つが超過プロパティチェックです。これは、オブジェクトが作成され、作成中にオブジェクト型に割り当てられるときに、オブジェクトをより徹底的に検証します。
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
に渡された引数は、color
ではなくcolour
とスペルミスされています。プレーンな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
でない限り、その型は問題ないことを示しています。
これらのチェックを回避するもう1つの方法(少し驚くかもしれませんが)は、オブジェクトを別の変数に割り当てることです。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
に由来するため、読者はこれらの2つの型が何らかの方法で関連していることを知ることができます。
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 });
インターフェースと交差型の比較
類似していますが、実際には微妙に異なる2つの型の組み合わせ方法を見てきました。インターフェースでは、extends
句を使用して他の型から拡張することができ、交差型でも同様のことを行い、型エイリアスで結果に名前を付けることができました。両者の主な違いは、競合の処理方法であり、その違いは通常、インターフェースと交差型の型エイリアスのどちらを選択するかを決定する主な理由の1つです。
ジェネリックオブジェクト型
任意の値(string
、number
、Giraffe
など)を含むことができる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 ());
型安全なアプローチの1つは、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
のすべてのインスタンスをstring
に置き換え、{ 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
型
ジェネリックオブジェクト型は、多くの場合、含まれる要素の型とは独立して動作する何らかのコンテナ型です。データ構造がこのように動作すると、異なるデータ型間で再利用できるため理想的です。
このハンドブック全体を通して、まさにそのような型を使用してきました。それは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 ;
タプル型
タプル型は、含まれる要素の数と、特定の位置に含まれる型を正確に知っている別の種類のArray
型です。
tsTry
typeStringNumberPair = [string, number];
ここで、StringNumberPair
はstring
とnumber
のタプル型です。ReadonlyArray
と同様に、ランタイムでは表現されませんが、TypeScriptにとって重要です。型システムにとって、StringNumberPair
は、インデックス0
にstring
、インデックス1
にnumber
を含む配列を表します。
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
を宣言するArray
のバリエーションである型と同等です。
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`);}
タプルにはrest要素を含めることもでき、rest要素は配列/タプル型でなければなりません。
tsTry
typeStringNumberBooleans = [string, number, ...boolean[]];typeStringBooleansNumber = [string, ...boolean[], number];typeBooleansStringNumber = [...boolean[], string, number];
StringNumberBooleans
は、最初の2つの要素がそれぞれstring
とnumber
であるが、その後に任意の数のboolean
を持つ可能性のあるタプルを表します。StringBooleansNumber
は、最初の要素がstring
で、その後任意の数のboolean
があり、最後にnumber
で終わるタプルを表します。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]
として推論されたため、その型はpoint
の要素が変更されないことを保証できないため、[number, number]
と互換性がありません。