【TypeScript】タプル型(Tuple)完全ガイド|基本・名前付き・可変長・readonly・実践パターンまで徹底解説

【TypeScript】タプル型(Tuple)完全ガイド|基本・名前付き・可変長・readonly・実践パターンまで徹底解説 TypeScript

TypeScriptのタプル型(Tuple)は、「要素数と各要素の型が固定された配列」を表す型です。[string, number] のように宣言し、「1番目は必ず文字列、2番目は必ず数値」という制約を型レベルで表現できます。Reactの useState が返す [value, setter] のペア、関数から複数の値を返すパターン、CSVの行データなど、実務で頻繁に登場します。TypeScript 4.0ではタプルに大きな改善(名前付き要素・可変長タプル)が加わり、さらに実用的になりました。本記事ではタプル型の基本から応用まで完全解説します。

この記事でわかること

  • タプル型の基本構文と Array 型との違い
  • インデックスアクセスとタプルの型安全性
  • TypeScript 4.0で追加された名前付きタプル(labeled tuple)
  • 可変長タプル(rest elements)と ...T の使い方
  • readonly タプルとイミュータブルなデータ表現
  • 分割代入(destructuring)とタプルの型推論
  • as const でタプル型を推論させるテクニック
  • 関数の複数戻り値・Reactカスタムフックでの実践パターン
スポンサーリンク

1. タプル型の基本構文

タプル型は [型1, 型2, ...] の形式で宣言します。各位置(インデックス)の型が個別に決まっており、要素数も固定されます。

// タプル型の宣言
let point: [number, number] = [10, 20];  // OK
let entry: [string, number] = ["Alice", 25];  // OK

// 型エラーの例
let bad1: [string, number] = [25, "Alice"];  // ❌ 順序が違う
let bad2: [string, number] = ["Alice", 25, true];  // ❌ 要素数が多い
let bad3: [string, number] = ["Alice"];  // ❌ 要素数が少ない

1-1. 配列型との違い

string[](配列型)とタプル型は似ていますが、役割が異なります。配列型は「同じ型の要素が可変個」、タプル型は「異なる型の要素が固定個」です。

比較項目 配列型 T[] タプル型 [T1, T2]
要素数 可変(0個以上) 固定
要素の型 全要素が同じ型 各位置で異なる型を持てる
型の情報量 低い(位置情報なし) 高い(位置ごとに型が決まる)
主な用途 同種データのリスト 異種データの組・複数戻り値
number[]: 数値の配列 [string, number]: 名前と年齢のペア
// 配列型: 要素数・型が緩い
const arr: string[] = ["a", "b", "c"];  // 何個でもOK
arr.push("d");  // OK

// タプル型: 要素数・型が厳密
const tpl: [string, number] = ["Alice", 25];
const name: string = tpl[0];  // OK: string
const age:  number = tpl[1];  // OK: number

// タプル型は位置ごとの型が確定している
const val0 = tpl[0];  // 型推論: string
const val1 = tpl[1];  // 型推論: number

// 存在しないインデックスはエラー
const val2 = tpl[2];  // ❌ Tuple type '[string, number]' of length '2' has no element at index '2'

1-2. オプショナルな要素

? を付けることでオプショナルな末尾要素を定義できます。オプショナル要素は 型 | undefined として扱われます。

// 末尾の要素がオプショナル
type RGB  = [number, number, number];
type RGBA = [number, number, number, number?];

const red:  RGB  = [255, 0, 0];
const blue: RGBA = [0, 0, 255];         // 4番目省略OK
const semi: RGBA = [0, 0, 255, 0.5];    // 4番目あり

// 型推論: alpha は number | undefined
const alpha = semi[3];  // number | undefined

// ❌ オプショナルは末尾のみ(途中には置けない)
type Bad = [string?, number];  // エラー: optional element must be last

2. 名前付きタプル(Labeled Tuple)— TypeScript 4.0

TypeScript 4.0で追加された名前付きタプルは、各要素にラベル(名前)を付けられる機能です。型そのものの動作は変わりませんが、IDEのホバー表示やエラーメッセージが読みやすくなり、コードの意図が伝わりやすくなります。

// 名前なしタプル: 何を表しているか分かりにくい
type Point2D = [number, number];

// 名前付きタプル: 意味が明確
type Point2DLabeled = [x: number, y: number];
type RGB             = [red: number, green: number, blue: number];
type UserEntry       = [name: string, age: number, email: string];

// 動作は同じ
const p: Point2DLabeled = [10, 20];
console.log(p[0]);  // 10 (x)
console.log(p[1]);  // 20 (y)

// IDEのホバー表示: [x: number, y: number] と表示される
// 名前なしの場合: [number, number] と表示される

2-1. 名前付きタプルとオーバーロード

名前付きタプルは関数のオーバーロードシグネチャの可読性向上にも役立ちます。特にパラメータをタプル型で表現するパターンで効果的です。

// 名前付きタプルで関数パラメータを型定義
type SetUser = [name: string, age: number, isAdmin?: boolean];

function createUser(...args: SetUser) {
  const [name, age, isAdmin = false] = args;
  return { name, age, isAdmin };
}

// IDEの補完: createUser(name: string, age: number, isAdmin?: boolean)
// → 名前なしだと createUser(args_0: string, args_1: number, args_2?: boolean)
createUser("Alice", 25);           // OK
createUser("Bob",   30, true);     // OK

// オーバーロードの例
type Handlers =
  | [event: "click",  handler: (e: MouseEvent) => void]
  | [event: "keydown", handler: (e: KeyboardEvent) => void];

function on(...args: Handlers) {
  const [event, handler] = args;
  document.addEventListener(event, handler as EventListener);
}

on("click",   (e) => console.log(e.clientX));  // e は MouseEvent
on("keydown", (e) => console.log(e.key));       // e は KeyboardEvent

3. 可変長タプル(Variadic Tuple Types)— TypeScript 4.0

TypeScript 4.0で追加された可変長タプル(variadic tuple types)では、スプレッド構文 ...T を使って別のタプル・配列型をタプル内に展開できます。複数のタプルを結合したり、汎用的なタプル操作関数を型安全に実装できます。

// 基本: タプル内でスプレッドを使って型を展開
type Head = [string, number];
type Tail = [boolean, Date];
type Combined = [...Head, ...Tail];  // [string, number, boolean, Date]

// 先頭・末尾に要素を追加
type Prepend<T extends unknown[], E> = [E, ...T];
type Append<T extends unknown[],  E> = [...T, E];

type WithId    = Prepend<[string, number], number>; // [number, string, number]
type WithExtra = Append<[string, number],  boolean>; // [string, number, boolean]

// 関数の引数にタプルを使う
function first<T extends unknown[]>(tuple: [T[0], ...T]): T[0] {
  return tuple[0];
}

3-1. タプルを使った汎用的なユーティリティ

// 先頭要素の型を取り出す
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;

// 末尾以外(先頭〜末尾の1つ前)を取り出す
type Init<T extends unknown[]> = T extends [...infer I, unknown] ? I : never;

// 末尾要素の型を取り出す
type Last<T extends unknown[]> = T extends [...unknown[], infer L] ? L : never;

// 先頭を除いた残り(Tail)
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never;

// タプルの長さ
type Length<T extends unknown[]> = T["length"];

// 使用例
type T = [string, number, boolean, Date];
type H = Head<T>;   // string
type Ta = Tail<T>;  // [number, boolean, Date]
type L = Last<T>;   // Date
type In = Init<T>;  // [string, number, boolean]
type N = Length<T>; // 4

3-2. 関数の引数操作(Parameters / rest)

// 既存関数に引数を追加するラッパー
type AddFirst<F extends (...args: any[]) => any, T> =
  (first: T, ...args: Parameters<F>) => ReturnType<F>;

function greet(name: string, greeting: string): string {
  return `${greeting}, ${name}!`;
}

// greet に id: number を先頭に追加した型
type GreetWithId = AddFirst<typeof greet, number>;
// (first: number, name: string, greeting: string) => string

// 可変長タプルで可変長引数を受け取るラッパー関数
function withLogging<T extends unknown[], R>(
  fn: (...args: T) => R,
  ...args: T
): R {
  console.log("calling with:", args);
  const result = fn(...args);
  console.log("result:", result);
  return result;
}

withLogging(greet, "Alice", "Hello");  // OK: T = [string, string]

4. readonly タプル

readonly [T1, T2] と宣言することで、要素の変更・追加・削除を禁止したイミュータブルなタプルを作れます。as const で推論されるタプル型も readonly になります。

// readonly タプルの宣言
const point: readonly [number, number] = [10, 20];

point[0] = 99;    // ❌ Cannot assign to '0' because it is a read-only property
point.push(30);   // ❌ Property 'push' does not exist on type 'readonly [number, number]'

// Readonly<T> ユーティリティ型との違い
type MutableTuple = [string, number];
type ReadonlyTuple = Readonly<[string, number]>;
// Readonly<T> は readonly [string, number] と同じ意味

// as const で推論: readonly [10, 20] になる(リテラル型に)
const pos = [10, 20] as const;
// pos の型: readonly [10, 20]  ← リテラル型のreadonlyタプル
// [number, number] とは異なる!

// as const なし: number[] と推論される(タプルにならない)
const posArr = [10, 20];  // number[] として推論される

4-1. readonly タプルと通常タプルの代入互換性

// mutable → readonly への代入: OK
const mutable: [number, number] = [1, 2];
const ro: readonly [number, number] = mutable;  // OK

// readonly → mutable への代入: ❌
const roFixed: readonly [number, number] = [3, 4];
const mut: [number, number] = roFixed;  // ❌ 型エラー

// 関数パラメータに readonly を使って安全に受け取る
function sumPoint(p: readonly [number, number]): number {
  return p[0] + p[1];
}

// mutable/readonly どちらも渡せる
sumPoint([10, 20]);        // OK
sumPoint(mutable);         // OK
sumPoint(roFixed);         // OK
関数パラメータのタプルには readonly を付けると安全
タプルを受け取る関数のパラメータには readonly [T1, T2] を使うと、関数内での変更を禁止しつつ、mutable・readonlyの両方を受け取れます。Reactの useState の返り値が readonly [S, Dispatch>]になっているのも同じ理由です。

5. 分割代入(Destructuring)とタプルの型推論

タプル型は分割代入と相性が良く、各要素の型が正確に推論されます。変数名を自由に決められるため、配列の分割代入との混乱を避けられます。

// 基本的な分割代入
const entry: [string, number] = ["Alice", 25];
const [name, age] = entry;
// name: string, age: number と正確に推論

// 一部の要素をスキップ
const rgb: [number, number, number] = [255, 128, 0];
const [red, , blue] = rgb;  // 2番目をスキップ
// red: number, blue: number

// デフォルト値の設定(オプショナル要素)
const rgba: [number, number, number, number?] = [255, 0, 0];
const [r, g, b, a = 1.0] = rgba;
// r: number, g: number, b: number, a: number

// ネストしたタプルの分割代入
const nested: [[number, number], string] = [[10, 20], "point"];
const [[x, y], label] = nested;
// x: number, y: number, label: string

// rest パターンで末尾を別のタプルに
const long: [string, number, boolean, Date] = ["a", 1, true, new Date()];
const [first, ...rest] = long;
// first: string, rest: [number, boolean, Date]

5-1. 関数の戻り値で分割代入

// タプルを返す関数 + 分割代入
function getMinMax(nums: number[]): [min: number, max: number] {
  return [Math.min(...nums), Math.max(...nums)];
}

// 自由な変数名で受け取れる
const [lo, hi]         = getMinMax([3, 1, 4, 1, 5, 9]);
const [minimum, maximum] = getMinMax([10, 20, 30]);
// lo: number, hi: number (名前付きタプルのラベルは参考情報)

// オブジェクトの戻り値と比較
function getMinMaxObj(nums: number[]) {
  return { min: Math.min(...nums), max: Math.max(...nums) };
}
// const { min, max } = getMinMaxObj([...]);
// → 変数名を変えるには: const { min: lo, max: hi } = ...
// タプルは変数名を自由に付けられるのが利点

6. as const とタプル型推論

TypeScriptは配列リテラルをデフォルトで T[] として推論します。タプル型として推論させるには as const が必要です。あるいは明示的に型注釈を付けます。

// as const なし: 配列型として推論される
const pair1 = ["Alice", 25];        // string | number)[] に推論
const pair2 = [10, 20];             // number[] に推論

// as const あり: readonly タプルとして推論される
const pair3 = ["Alice", 25] as const; // readonly ["Alice", 25] に推論
const pair4 = [10, 20]      as const; // readonly [10, 20] に推論

// 型注釈あり: タプル型として扱われる
const pair5: [string, number] = ["Alice", 25]; // [string, number]

// as const の場合はリテラル型になる
const p3_0 = pair3[0];  // "Alice" (string ではなくリテラル型)
const p5_0 = pair5[0];  // string

6-1. as const タプルをルートとして使う実践パターン

// 設定値をタプルで管理
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"] as const;
// 型: readonly ["GET", "POST", "PUT", "DELETE", "PATCH"]

type HttpMethod = typeof HTTP_METHODS[number];
// "GET" | "POST" | "PUT" | "DELETE" | "PATCH"

// ペアのマッピングをタプルで管理
const STATUS_MESSAGES = [
  [200, "OK"],
  [404, "Not Found"],
  [500, "Internal Server Error"],
] as const;
// 型: readonly (readonly [200, "OK"] | readonly [404, "Not Found"] | ...)[]

type StatusEntry = typeof STATUS_MESSAGES[number];
// readonly [200, "OK"] | readonly [404, "Not Found"] | ...

function getStatusMessage(code: StatusEntry[0]): StatusEntry[1] {
  const entry = STATUS_MESSAGES.find(([c]) => c === code);
  return entry ? entry[1] : "Unknown";
}

as const の詳細については 型アサーション(as・as const・satisfies)完全ガイド も参照してください。

7. 関数の複数戻り値としてのタプル

TypeScriptで関数から複数の値を返す方法は「オブジェクトで返す」と「タプルで返す」の2つがあります。用途に応じて使い分けることが重要です。

比較項目 タプルで返す オブジェクトで返す
受け取り側の変数名 自由に命名できる プロパティ名に合わせる必要あり
コードの簡潔さ const [a, b] = fn() const { x, y } = fn()
要素数が多い場合 位置の意味が不明瞭になる プロパティ名で意味が明確
IDEの補完 名前付きタプルで改善 プロパティ名で自然
主な用途 2〜3要素の小さなペア 3要素以上の複雑なデータ
要素数が3を超えたらオブジェクトを検討する
タプルは要素数が増えると [value, error, loading, refetch, reset] のように「何番目が何か」が分かりにくくなります。経験則として 2〜3要素まではタプル、4要素以上はオブジェクト が読みやすいコードの目安です。ただし名前付きタプルを使うと要素が多くても意味が伝わりやすくなるため、「同じ型を返すカスタムフック」など、自由な命名が重要な場面ではタプルが有効です。
// タプルで返す(2〜3要素に適している)
function parseCoord(input: string): [x: number, y: number] {
  const [xs, ys] = input.split(",");
  return [parseFloat(xs), parseFloat(ys)];
}
const [px, py] = parseCoord("10.5,20.3");

// オブジェクトで返す(要素数が多い場合に適している)
function parseUser(input: string) {
  const [name, age, email] = input.split(",");
  return { name, age: Number(age), email };
}
const { name, age, email } = parseUser("Alice,25,alice@example.com");

// 処理結果と付加情報をペアで返す
function findUser(id: number): [user: User | null, error: string | null] {
  const user = db.find(u => u.id === id) ?? null;
  const error = user ? null : `User ${id} not found`;
  return [user, error];
}

const [user, error] = findUser(42);
if (error) {
  console.error(error);
} else {
  console.log(user?.name);
}

8. 実践例:React カスタムフックでのタプル

Reactの useState[state, setState] のタプルを返すのは有名ですが、カスタムフックでも同じパターンを使うことで、呼び出し側が変数名を自由に決められるという利点が生まれます。

8-1. useState の型定義を理解する

// useState の型シグネチャ(簡略版)
declare function useState<S>(initialState: S): [S, Dispatch<SetStateAction<S>>];
// または
declare function useState<S>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];

// 使用例: 変数名を自由に付けられる
const [count,    setCount]    = useState(0);
const [username, setUsername] = useState("");
const [isOpen,   setIsOpen]   = useState(false);
// オブジェクトなら { value, setValue } と固定名になってしまう

8-2. カスタムフックの戻り値をタプルにする

// トグルフック
function useToggle(initial = false): [boolean, () => void, (v: boolean) => void] {
  const [value, setValue] = useState(initial);
  const toggle  = () => setValue(v => !v);
  const set     = (v: boolean) => setValue(v);
  return [value, toggle, set];
}

// 呼び出し側: 変数名を自由に決められる
const [isOpen,    toggleOpen,    setOpen]    = useToggle();
const [isVisible, toggleVisible, setVisible] = useToggle(true);

// ローカルストレージフック
function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T) => void, () => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? (JSON.parse(item) as T) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    setStoredValue(value);
    window.localStorage.setItem(key, JSON.stringify(value));
  };

  const removeValue = () => {
    setStoredValue(initialValue);
    window.localStorage.removeItem(key);
  };

  return [storedValue, setValue, removeValue];
}

// 呼び出し側
const [theme,  setTheme,  removeTheme]  = useLocalStorage("theme", "light");
const [userId, setUserId, removeUserId] = useLocalStorage("userId", "");

8-3. 名前付きタプルでカスタムフックの可読性を上げる

// 名前付きタプルで戻り値の意図を明示
function useCounter(initial = 0): [
  count: number,
  increment: () => void,
  decrement: () => void,
  reset: () => void
] {
  const [count, setCount] = useState(initial);
  return [
    count,
    () => setCount(c => c + 1),
    () => setCount(c => c - 1),
    () => setCount(initial),
  ];
}

// IDE補完: useCounter() が [count: number, increment: () => void, ...] と表示
const [cartCount, addToCart, removeFromCart, clearCart] = useCounter(0);
const [pageNum,   nextPage,  prevPage,        resetPage] = useCounter(1);

React + TypeScriptの型定義パターン全般については React + TypeScriptの始め方 も参照してください。

9. 実践例3本

実践例1:CSVパーサーの型安全な実装

CSVの各行を固定スキーマのタプルとして扱うことで、パース結果を型安全に処理する実装例です。

// CSVスキーマをタプル型で定義
type UserRow    = [id: number, name: string, age: number, email: string];
type ProductRow = [code: string, name: string, price: number, stock: number];

// 汎用CSVパーサー
function parseCSV<T extends readonly unknown[]>(
  csv: string,
  parser: (cols: string[]) => T
): T[] {
  return csv
    .trim()
    .split("\n")
    .slice(1)                   // ヘッダー行をスキップ
    .map(line => parser(line.split(",")));
}

// UserRow パーサー
const parseUserRow = (cols: string[]): UserRow => [
  Number(cols[0]),
  cols[1].trim(),
  Number(cols[2]),
  cols[3].trim(),
];

const csvData = `id,name,age,email
1,Alice,25,alice@example.com
2,Bob,30,bob@example.com`;

const users = parseCSV(csvData, parseUserRow);
// users: UserRow[] = [number, string, number, string][]

for (const [id, name, age, email] of users) {
  console.log(`${id}: ${name} (${age}) - ${email}`);
  // 各変数は正確な型で推論される
}

実践例2:フォームバリデーション結果をタプルで管理

フォームのバリデーション結果を [isValid: boolean, errors: Record<string, string>] のタプルで返すパターンです。バリデーション関数の戻り値がタプルなので、分割代入で結果とエラーを簡潔に受け取れます。

type FieldName = "username" | "email" | "password";
type ValidationResult = [isValid: boolean, errors: Partial<Record<FieldName, string>>];

interface FormValues {
  username: string;
  email:    string;
  password: string;
}

function validateForm(values: FormValues): ValidationResult {
  const errors: Partial<Record<FieldName, string>> = {};

  if (values.username.length < 3) {
    errors.username = "ユーザー名は3文字以上で入力してください";
  }
  if (!/^[^@]+@[^@]+\.[^@]+$/.test(values.email)) {
    errors.email = "正しいメールアドレスを入力してください";
  }
  if (values.password.length < 8) {
    errors.password = "パスワードは8文字以上で入力してください";
  }

  const isValid = Object.keys(errors).length === 0;
  return [isValid, errors];
}

// 呼び出し側: 分割代入で簡潔に受け取る
const [isValid, errors] = validateForm({
  username: "Al",
  email:    "invalid-email",
  password: "short",
});

if (!isValid) {
  if (errors.username) console.error("username:", errors.username);
  if (errors.email)    console.error("email:",    errors.email);
  if (errors.password) console.error("password:", errors.password);
}

// Reactでの使用例: バリデーション結果をステートに反映
function RegistrationForm() {
  const [formErrors, setFormErrors] = useState<Partial<Record<FieldName, string>>>({});

  const handleSubmit = (values: FormValues) => {
    const [valid, errs] = validateForm(values);
    if (!valid) {
      setFormErrors(errs);
      return;
    }
    // 送信処理...
  };
}

実践例3:型安全なイベントエミッター

タプル型を使ってイベント名と引数の型を紐づけた、型安全なイベントエミッターの実装例です。

// イベントマップをタプルで定義
interface EventMap {
  userCreated: [id: number, name: string];
  orderPlaced: [orderId: string, total: number, itemCount: number];
  error:       [message: string, code?: number];
}

// 型安全なイベントエミッター
class TypedEventEmitter<Events extends Record<string, readonly unknown[]>> {
  private listeners = new Map<keyof Events, ((...args: unknown[]) => void)[]>();

  on<K extends keyof Events>(
    event: K,
    handler: (...args: Events[K]) => void
  ): this {
    const arr = this.listeners.get(event) ?? [];
    arr.push(handler as (...args: unknown[]) => void);
    this.listeners.set(event, arr);
    return this;
  }

  emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
    this.listeners.get(event)?.forEach(h => h(...(args as unknown[])));
  }
}

// 使用例
const emitter = new TypedEventEmitter<EventMap>();

emitter.on("userCreated", (id, name) => {
  // id: number, name: string と正確に推論
  console.log(`User created: ${id} - ${name}`);
});

emitter.on("orderPlaced", (orderId, total, itemCount) => {
  console.log(`Order ${orderId}: ${itemCount} items, $${total}`);
});

emitter.emit("userCreated", 1, "Alice");           // OK
emitter.emit("orderPlaced", "ORD-001", 99.9, 3);   // OK
emitter.emit("userCreated", "wrong", 1);            // ❌ 型エラー

10. まとめ:タプル型チートシート

構文 説明
[T1, T2] 基本のタプル型 [string, number]
[T1, T2?] 末尾オプショナル要素 [number, number, number?]
[l1: T1, l2: T2] 名前付きタプル(TS 4.0) [x: number, y: number]
[T1, ...T2[]] 可変長末尾要素 [string, ...number[]]
[...T1, T2] 可変長先頭要素 [...string[], number]
readonly [T1, T2] readonlyタプル readonly [string, number]
const x = [...] as const readonlyリテラルタプルに推論 readonly ["a", 1]
T["length"] タプルの長さを型として取得 [string, number]["length"]2
T[number] タプルの要素型のUnion [string, number][number]string | number
T[0], T[1] 特定インデックスの要素型 [string, number][0]string

型推論と infer の組み合わせについては 型推論(Type Inference)完全ガイド、型の基本(Unionなど)については 型の書き方 完全入門 も参照してください。

FAQ

Qタプル型と配列型はどちらを使えばよいですか?

A要素数が固定で各位置に意味がある場合(座標のペア・関数の複数戻り値・カスタムフックの戻り値など)はタプル型、要素数が可変で全要素が同じ型の場合(IDのリスト・文字列の配列など)は配列型を使います。3要素を超えるとタプルは位置の意味が分かりにくくなるため、要素数が多い場合はオブジェクト型かインターフェースを検討してください。

Qas const で推論されるタプルとタプル型注釈の違いは何ですか?

Aas const は各要素がリテラル型("Alice"25)になるreadonly タプルを推論します。一方、型注釈 [string, number] は各要素が汎用型のミュータブルなタプルです。変更可能なタプルには型注釈を、定数として扱うデータには as const を使ってください。

Q名前付きタプルのラベルはランタイムに影響しますか?

Aいいえ。名前付きタプルのラベルは型情報のみで、コンパイル後のJavaScriptには残りません。ランタイムの動作は通常の配列と同じです。ラベルはIDEの補完・ホバー表示・エラーメッセージを読みやすくするためだけに存在します。

Q可変長タプル(rest elements)はタプルの途中に置けますか?

ATypeScript 4.2以降、rest elements はタプルの先頭・中間・末尾のどこにでも置けます。ただし、string?(オプショナル要素)の後に rest を置くことはできません。例: [...string[], number, ...boolean[]] は TS 4.2以降で有効です。

Qタプル型を配列のメソッド(map・filter)と使うとどうなりますか?

Amapfilter を使うと戻り値の型はタプルではなく配列型になります。例えば [string, number].map(x => x) の戻り値は (string | number)[] です。タプルの型情報を保ったまま変換するには、タプルに対して直接処理するか、as で型アサーションするか、型ユーティリティを使う必要があります。

Qタプル型の長さは型として取得できますか?

Aはい。T["length"] でタプルの要素数をリテラル型として取得できます。[string, number, boolean]["length"]3(数値リテラル型)になります。これを条件型と組み合わせることで、タプルの長さに基づく型操作が可能です。詳細は 条件型(Conditional Types)完全ガイド を参照してください。