TypeScriptを勉強していると、「タプル(Tuple)」という言葉が出てきます。見た目は配列と同じ [ ] なので、最初は違いが分かりにくいところです。
C言語でいえば、「メンバー名のない構造体」に近いものです。この記事では、タプルと配列の違い、使いどころ、実際に使うときにつまずきやすい点を、動くコードで確かめます。コードはTypeScript Playgroundに貼れば、そのまま試せます。
タプルとは:仕切りの決まったお弁当箱
タプルは、「要素の数と、何番目にどの型が入るかが決まった配列」です。身近なものにたとえると、次のようになります。
- 配列:
バイキングのお皿。好きなものを、好きな順番で、いくつでも載せられる - タプル:
仕切りの決まったお弁当箱。1つ目の仕切りにはご飯、2つ目にはおかず、のように入れる場所と数が決まっている
コードで見る配列とタプルの違い
配列
// 文字列と数値が混ざった配列
const arr1: (string | number)[] = ["田中", 25];
// 順番を逆にしてもエラーにならない
const arr2: (string | number)[] = [25, "田中"];
// 要素をいくつでも追加できる
arr1.push("東京");(string | number)[] は「文字列か数値が入る配列」という意味です。入る型は決められますが、順番や要素の数までは決められません。
タプル
// 1番目は string(名前)、2番目は number(年齢)
const tuple1: [string, number] = ["田中", 25];
// 順番を逆にするとエラー
const tuple2: [string, number] = [25, "田中"];
// 要素の数が違うとエラー
const tuple3: [string, number] = ["田中", 25, "東京"];
// ない番号を読もうとするとエラー
console.log(tuple1[2]);Playgroundでは、次のようなエラーが出ます(一部を抜粋)。
Type 'number' is not assignable to type 'string'.
Type '[string, number, string]' is not assignable to type '[string, number]'.
Source has 3 element(s) but target allows only 2.
Tuple type '[string, number]' of length '2' has no element at index '2'.上から順に「1番目に数値は入れられない」「要素が3つあるが2つまで」「番号2の要素はない」という意味です。[string, number] と書くだけで、数と順番の間違いをコンパイルのときに見つけられます。
なお、タプルは TypeScript の型の上だけの区別です。JavaScriptに変換されて実行されるときは、ただの配列になります。
タプルの使いどころ
名前と年齢のようなデータは、ふつうは { userName: "田中", age: 25 } のようなオブジェクトで表します。プロパティ名があるほうが、あとで読んだときに意味が分かるからです。タプルが向いているのは、次のような場面です。
① 関数から複数の値を返して、受け取る側で名前を付けたいとき
Reactの useState は、この形の代表例です。
// useState の戻り値は、[今の値, 値を更新する関数] というタプル
const [count, setCount] = useState(0);タプルで返すと、受け取る側が [count, setCount] のように好きな名前で取り出せます。同じ画面で useState を何度も使うときも、[userName, setUserName] のようにそれぞれ名前を付けられるのが便利です。[a, b] = ... の取り出し方(分割代入)は、「文字列のデータをオブジェクトの配列に変える処理」でも使っています。
② 座標のように、順番だけで意味が分かる短い組
// [x座標, y座標]
const point: [number, number] = [10, 20];
// [緯度, 経度](東京駅のあたり)
const tokyoStation: [number, number] = [35.6812, 139.7671];「x、yの順」のように、誰が見ても順番が分かる短い組なら、タプルでも読みやすさは落ちません。逆に、要素が3つを超えたり、順番を覚えないと意味が分からなかったりするときは、オブジェクトにしたほうが安全です。
つまずきやすいこと
型を書かないと、タプルではなく配列になる
関数で ["田中", 25] のように返しても、戻り値の型を書かないと、TypeScriptはタプルではなく「文字列か数値の配列」として扱います。
function getUser() {
return ["田中", 25]; // 型は (string | number)[] になる
}
const [userName, age] = getUser();
// userName も age も、string | number 型になってしまう
age.toFixed(1); // エラー:age が文字列かもしれないので toFixed を呼べないタプルとして扱わせるには、戻り値の型を書くか、as const を付けます。
// 方法1:戻り値の型を書く
function getUser1(): [string, number] {
return ["田中", 25];
}
// 方法2:as const を付ける(readonly ["田中", 25] という型になる)
function getUser2() {
return ["田中", 25] as const;
}
const [name1, age1] = getUser1();
age1.toFixed(1); // OK:age1 は number 型as const を付けると、中身もその値そのもの("田中" や 25)の型になり、書き換えもできなくなります。途中で値を変える予定があるなら、方法1のように型を書くほうが扱いやすいです。
ふつうのタプルは push できてしまう
要素の数が決まっているはずのタプルでも、push はエラーになりません。変更されたくないときは、readonly を付けます。
const pair: [number, number] = [10, 20];
pair.push(30); // エラーにならない(実行すると要素が3つになる)
const fixedPair: readonly [number, number] = [10, 20];
fixedPair.push(30); // エラー:push は使えない
fixedPair[0] = 5; // エラー:書き換えもできない知っておくと便利な書き方
ラベル付きタプル
それぞれの要素に名前(ラベル)を付けられます。動きは変わりませんが、エディタで型を確かめたときにラベルが表示されるので、何番目が何か分かりやすくなります。
type LatLng = [lat: number, lng: number];
const osakaStation: LatLng = [34.7025, 135.4959];省略できる要素
? を付けると、その要素は省略できます。省略できるのは、後ろのほうの要素だけです。
// Range という名前はブラウザの型とぶつかるので、NumberRange にしている
type NumberRange = [start: number, end?: number];
const r1: NumberRange = [1, 10]; // OK
const r2: NumberRange = [1]; // OK:end を省略まとめ
- タプルは、要素の数と順番ごとの型が決まった配列。実行するときはただの配列
- 配列は「同じ種類のものをいくつでも」、タプルは「決まった形の少ない組」に向いている
- 意味が分かりにくくなるときは、プロパティ名のあるオブジェクトを使う
- 関数から返すときは、戻り値の型を書くか
as constを付けないと配列になる - 変更させたくないときは
readonly、意味を分かりやすくしたいときはラベル付きタプル
関数の書き方(=>)は「アロー関数(=>)とは?」で紹介しています。



コメント