【TypeScript】タプル型の使い方
更新日:2022/11/09
TypeScriptにはタプル型という型があります。
タプル(tuple)とは、複数の要素を順番に並べて一つの要素として扱うことを指す用語です。
プログラム言語では配列がイメージとして近いです。
一部の言語はタプル型という名称で、タプルが定義されています。
今回は、TypeScriptのタプル型についてお伝えします。
TypeScriptのタプル型
TypeScriptのタプル型は、要素の位置と型を保持する型です。
次のように、配列表記で要素の型を記述します。
タプル型
let tuple1:[number,string];
let tuple2:[number,string,boolean];
これで、tuple1に [number,string] というタプル型が、tuple2に [number,string,boolean] というタプル型が型付けされます。
異なる要素を持つ配列(タプル)を代入すると、エラーです。
tuple1 = [ 100 , "abc" ];
tuple1 = [ 200 , "hello" ];
tuple1 = [ "hello" , "abc"]; // エラー: Type 'string' is not assignable to type 'number'.
※エラー内容: タイプ 'string' はタイプ 'number' に割り当てられません。
定義されていないインデックスにアクセスすると、エラーです。
console.log( tuple1[2] ); // エラー: Tuple type '[number, string]' of length '2' has no element at index '2'.
※エラー内容: 長さ '2' のタプル タイプ '[number, string]' には、インデックス '2' に要素がありません。
代入でもエラーです。
エラーが2つ表示されます。
tuple1[2] = "hello"; // エラー: Type '"hello"' is not assignable to type 'undefined'.
// エラー: Tuple type '[number, string]' of length '2' has no element at index '2'.
※エラー内容: タイプ '"hello"' はタイプ 'undefined' に割り当てられません。
タプル型は型コンテキストで明示的に型付けする必要があります。
型を指定しないで配列を代入した場合は、配列型です。
定義されていないインデックスにアクセスできるので、タプル型ではありません。
(と思うのだけど、もしかしたらタプル型に含まれているかもしれない)
配列型:タプル型ではない
let nonTuple = [ 1 , 2 , 3]; // nonTuple: number[]
nonTuple[100] = 100; // 範囲外にアクセスできる:タプル型ではない
読み込み専用(readonly)タプル型
タプル型は、readonly修飾子を付けると読み込み専用として定義できます。
要素を変更するとエラー
読み込み専用タプルの要素を変更すると、エラーになります。
let tuple1: readonly[number,string] = [1,"hello"];
tuple1[0] = 2; // エラー: Cannot assign to '0' because it is a read-only property.
※エラー内容: 読み取り専用プロパティであるため、'0' に割り当てることはできません。
変数そのものは上書き可能
しかし、変数そのものは上書きできます。
let tuple1: readonly[number,string] = [1,"hello"];
tuple1 = [2,"bye"]; // エラーにならない
readonlyが無いタプル型との代入
要素数と型が同じタプル型で readonlyの有無が異なる場合、readonlyが無いタプル型からreadonlyタプル型への代入はできます。
ですが、その逆はできません。
let tuple1: readonly[number,string] = [1,"hello"];
let tuple2: [number,string]= [2,"bye"];
tuple1 = tuple2; // readonlyなし → readonlyあり : エラーなし
tuple2 = tuple1; // readonlyあり → readonlyなし
// エラー: The type 'readonly [number, string]' is 'readonly' and cannot be assigned to the mutable type '[number, string]'.
※エラー内容: タイプ 'readonly [number, string]' は 'readonly' であり、変更可能なタイプ '[number, string]' に割り当てることはできません。
配列の代入は同じ実体を共有します。
readonlyタプル型のデータを保護するために必要な仕様ですね。
引数に渡すときも同じようなエラーが表示されます。
やりがちなミスですね。
const func = (tuple:[number,string])=>{
tuple[0] = 1000;
};
func( tuple1 ); // エラー: Argument of type 'readonly [number, string]' is not assignable to parameter of type '[number, string]'.
※エラー内容: タイプ 'readonly [number, string]' の引数は、タイプ '[number, string]' のパラメーターに割り当てられません。
可変長タプル型
タプル型は、要素の型に ... を付けて可変長にできます。
let tuple1: [...number[]] = [1,2,3];
tuple1[3] = 4;
tuple1 = [ 100,200,300 ];
動作としては、number[]型と同じです。
let tuple1: number[] = [1,2,3];
tuple1[3] = 4;
tuple1 = [ 100,200,300 ];
少し分かりにくいですが、最初のコードは [...number[]] 、次のコードは number[] と記述されています。
後者のnumber[]は配列型ですね。
この用途なら後者で十分です。
可変長タプル型は、他の型を組み合わせて使用するとき効果的です。
let tuple1: [string,...number[],boolean];
tuple1 = ["a",1,2,3,false];
tuple1 = ["a",1,2,3,4,5,false];
tuple1 = [1,2,3]; // エラー: Type 'number' is not assignable to type 'string'.
可変長は一か所だけ指定できます。
上のコードは、最初がstring型、次が可変長のnumber型、最後にbooleanのタプル型を定義しています。
このパターンに一致しない配列を代入するとエラーになります。
要素に代入する場合、可変長の直前まで型チェックされます。
可変長以降はチェックされません。
let tuple1: [string,boolean,...number[],boolean];
tuple1 = ["a",true,1,2,3,false];
tuple1[0] = 100; // エラー: Type 'number' is not assignable to type 'string'.
tuple1[1] = 100; // エラー: Type 'number' is not assignable to type 'boolean'.
tuple1[2] = true; // エラーではない
tuple1[100] = 100; // エラーではない
上記のコードは、変数tuple1の添え字の2から可変長です。
そのため[0]と[1]の型がチェックされていますが、2以降はチェックされていません。
タプル型をタプル型内で展開
可変長タプル型と似ていますが、... を使ってタプル型をタプル型内で展開できます。
type T1 = [string,number];
type T2 = [string,...T1]; // T2 = [string, string, number]
type T3 = [...T1,...T2,string]; // T3 = [string, number, string, string, number, string]
可変長タプル型と組み合わせることもできます。
type T1 = [string,number];
type T2 = [string,...T1,...string[]]; // T2 = [string, string, number, ...string[]]
type T3 = [...T1,...T2,string]; // T3 = [string, number, string, string, number, ...string[], string]
ただし、可変長は1回だけなので注意が必要です。
他の型コンテキストと組み合わせすぎて、エラーにならないようにしましょう。
type T3 = [...T1,...T2,string,...string[] ]; // A rest element cannot follow another rest element.
※エラー内容: 残りの要素は、別の残りの要素に続くことはできません。
上のコードのT3は、次のように展開されます。
[ string, number, string, string, number, ...string[], stringr, ...string[] ]
...string[]が二か所あります。
可変長が2回なので、エラーです。
ラベル付きタプル要素
タプル型の各要素に、ラベルを付けることができます。
let tuple1: [name:string,flg:boolean];
ただし、一つでも名前を付けたら、全てに名前を付ける必要があります。
let tuple1: [name:string,flg:boolean,number]; // エラー:Tuple members must all have names or all not have names.
エラー内容:タプル メンバーはすべて名前を持つか、すべて名前を持たない必要があります。
名前を付けたら、その名前でアクセスできるような気がしますね。
でも、できません。
let tuple1: [name:string,flg:boolean]=["taro",true];
tuple1[flg]=false; // エラー: Cannot find name 'flg'.
※エラー内容: 名前 'flg' が見つかりません。
タプル型といっても、実体は配列なのでインデックスでアクセスする必要があります。
TypeScriptが名前をインデックスに変換してくれればいいのですが、そこまではやってくれません。
何のためにこんな機能があるのかというと、各要素の目的(使い道)をわかりやすくするためです。
型チェックには、影響しません。
更新日:2022/11/09
関連記事
スポンサーリンク
記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。