TypeScript | 基礎文法:配列・タプル – 配列に異なる型を入れた場合

TypeScript
スポンサーリンク

「配列に異なる型を入れる」と TypeScript はどう解釈するか

まず前提として、TypeScript は「配列の中身(要素)を見て、その配列の型を決める」言語です。
なので、配列に異なる型を混ぜると、その要素たちをまとめて表現できる型を自動的に作ろうとします。

const mixed = [1, "2", 3];
// 型: (number | string)[]
TypeScript

この場合、mixed は「number または string の配列」と推論されます。
つまり、「要素ごとに number か string かは違ってもいいけれど、そのどちらかであることは保証される」という状態です。

ここから、「ユニオン型の配列」と「タプル」との違いを意識していくのが大事なポイントになります。


ユニオン型の配列として扱われる場合

number と string が混ざった配列

const values = [1, "2", 3];

values.push(4);      // OK
values.push("5");    // OK
// values.push(true); // エラー
TypeScript

このときの型は (number | string)[] です。
「number か string のどちらかなら入れていい」という意味なので、true のような boolean は弾かれます。

ここで重要なのは、「配列の各要素は同じ型である必要はない」ということです。
TypeScript は、「この配列は number か string のどちらかを入れる場所」と理解しているので、
取り出したときもそのように扱われます。

const v = values[0]; // v: number | string
TypeScript

v は number か string か分からないので、そのままでは数値計算も文字列メソッドも安全に呼べません。
必要なら typeof で絞り込む(型ガードする)ことになります。


ユニオン型の配列と「配列のユニオン」の違い

(number | string)[] と number[] | string[] は別物

ここは初心者がつまずきやすいポイントなので、少し丁寧にいきます。

let a: (number | string)[]; // 要素ごとに number か string
let b: number[] | string[]; // 配列そのものが number[] か string[] のどちらか
TypeScript

(number | string)[] は、「1つの配列の中に number と string が混ざっていてもいい」型です。
一方 number[] | string[] は、「この変数には number の配列か string の配列のどちらかが入る」という意味で、
1つの配列の中で混ざることは想定していません。

a = [1, "2", 3];      // OK
// b = [1, "2", 3];   // NG(number[] でも string[] でもない)
b = [1, 2, 3];        // OK(number[])
b = ["a", "b"];       // OK(string[])
TypeScript

「要素が混ざる」のか、「配列の種類が2パターンある」のか——
ここを意識して型を書くと、配列の設計がかなりクリアになります。


「何でも入る配列」になってしまう危険パターン

空配列+異なる型の push

よくある落とし穴がこれです。

let xs = [];
xs.push(1);
xs.push("hello");
TypeScript

この場合、xsany[] になりがちです。
TypeScript は最初に「中身が分からない配列」として扱い、その後も「何でも入る配列」として扱ってしまいます。

any[] になると、型チェックがほぼ効かなくなります。

xs.push(true); // これも通ってしまう
TypeScript

「異なる型を入れたい」のと「何でもアリにしたい」のは別物です。
前者なら (number | string)[] のように意図を持ってユニオン型を書くべきで、
後者(何でもアリ)は基本的に避けるべきです。


「異なる型を入れたい」ときに考えるべきこと

本当に「1つの配列に混ぜる」のが正しいか?

たとえば、こんな配列を考えてみます。

const data = [1, "Taro", true];
TypeScript

型としては (number | string | boolean)[] になりますが、
これを見て「この配列、何の配列?」と聞かれたときに、うまく答えられるでしょうか。

「ユーザー情報の配列」なら、素直にオブジェクトにした方が意味が伝わりやすいです。

const user = {
  id: 1,
  name: "Taro",
  isActive: true
};
TypeScript

あるいは、複数人分なら User[] にします。

type User = {
  id: number;
  name: string;
  isActive: boolean;
};

const users: User[] = [
  { id: 1, name: "Taro", isActive: true },
  { id: 2, name: "Hanako", isActive: false }
];
TypeScript

「異なる型を1つの配列に詰め込む」のは、
「本当はオブジェクトや別の構造にした方がいいのに、無理やり配列にしている」サインであることも多いです。

それでも混ぜたいなら、ユニオン型として意図をはっきりさせる

たとえば、「ログの配列で、数値IDか文字列メッセージが入る」という仕様なら、
(number | string)[] と明示してしまうのはアリです。

type LogItem = number | string;

const logs: LogItem[] = [1, "start", 2, "end"];
TypeScript

このとき大事なのは、「この配列は LogItem の配列だ」と名前を付けてあげること。
そうすると、「何でもアリの配列」ではなく、「仕様としてこういう値が混ざる配列」だと読み手に伝わります。


タプルという選択肢もある(位置ごとに型が決まっている場合)

「1番目は string、2番目は number」のような配列

「異なる型を入れたい」と言っても、
「どの位置に何が入るかが決まっている」場合は、タプル型の方が適しています。

let user: [string, number] = ["Taro", 20];
TypeScript

これは「string と number が混ざった配列」ではなく、
「1番目は string、2番目は number」という位置に意味がある配列です。

複数人分なら、タプルの配列にできます。

let users: [string, number][] = [
  ["Taro", 20],
  ["Hanako", 18]
];
TypeScript

「順番に意味がある」「要素数が決まっている」ならタプル、
「順番に意味はなく、同じ型の値が並ぶ」なら配列、
「複数の型が混ざるならユニオン型の配列」——
このあたりの切り分けが見えてくると、設計が一気に気持ちよくなります。


まとめの感覚:「混ぜるなら、意図を型で表現する」

配列に異なる型を入れること自体は、TypeScript的には全然アリです。
ただし、そのときに大事なのは、

「何が混ざるのか」をユニオン型で明示する
「何でもアリの any[] に逃げない
「本当はオブジェクトやタプルの方が自然じゃないか?」と一度立ち止まる

という3つの視点です。

「この配列は何の配列か?」
「その“何”は、型としてどう表現できるか?」

そこまで言語化できたとき、配列はただの「値の並び」から、
あなたの設計意図をそのまま映した「型付きのデータ構造」に変わります。

タイトルとURLをコピーしました