Programmingフロントエンド開発者

関数を実装して、パラメータに応じてユニオン型またはタプルを返すように型付けするにはどうすればよいですか?厳格な型付けを失うことなく、戻り値を正しく記述するにはどうすればよいですか?

Hintsage AIアシスタントで面接を突破

回答。

問題の背景:

いくつかのタスクでは、関数が異なるデータ構造を返す必要があります—例えば、固定長の配列(タプル)またはいくつかの結果のバリエーション(ユニオン型)。ダイナミックなJavaScriptでは、このアプローチは「結果またはエラー」というパターン(例:[err, value])によく使用され、TypeScriptでは、後続のコードが結果の構造を正しく理解するために明確な型付けが必要です。

問題:

戻り値の型を明示的に指定しない場合、TypeScriptは結果をあまりにも「広い」形式(すなわち狭くない)に持っていきます—配列またはユニオンになり、それにより開発者は自動補完や型安全性の利点を失ってしまいます。関数の結果を分解すると、要素の型に関する正確な記述を失うリスクがあります。

解決策:

条件付き型と関数のオーバーロード記述を使用し、タプルについては戻り値の配列の型を明示的に設定し、定数タプルを返す場合はas constを使用する必要があります。ユニオン結果では、ジェネリクスと型のナロイングを組み合わせて、外部のコードで戻り値の型を正しく区別できるようにします。

コードの例:

type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Invalid number'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // タプルとas constによる自動的な構造: function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'error'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }

主な特徴:

  • パラメータに応じて異なる型を返す関数にはオーバーロードを使用します。
  • タプルの場合は、常に[T, U]またはas constを使用して、分解後の要素の長さと型を正確に保持します。
  • 戻り値のユニオン型は、さらなる作業の際に型を正しく定義するためにタイプガードを必要とします。

トリックのある質問。

分解後の配列要素の型は、常に具体的ですか?

いいえ、関数がユニオンタプルを返す場合、分解後の要素はすべての可能なバリエーションの型のユニオンを表します。

type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string

as constを使用することで、追加の型記述なしにタプルの型を完全に“固定”できますか?

いいえ、as constは値を固定しますが、関数が戻り値の型なしで宣言されている場合、TypeScriptは必要以上に広い型を推論してしまう可能性があります。戻り値の型を明示的に指定する方が良いです。

function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]

異なる戻り値の型を持つオーバーロードは、分解後の結果の型を保証しますか?

オーバーロードはコンパイラーを助けますが、入力パラメータが不明な場合、結果はすべてのオプションのユニオンになります。型を正確に絞り込むには、結果を処理する際にタイプガードを使用する必要があります。

function bar(x: number): string; function bar(x: boolean): number; function bar(x: any): any { return typeof x === 'number' ? 'str' : 123; } const r = bar(Math.random() > 0.5 ? true : 1); // r: number | string

型の間違いとアンチパターン

  • 関数をany[]または(T | U)[]を返すと記述することは、タプルの特異性を「ぼやけさせる」要因です。
  • タイプナロイングと要素の型の正当性チェックなしで分解を試みること。
  • 戻り値の構造を明示的に記述しないこと — サポートと自動補完の難易度を上げます。

実例

ネガティブケース

関数が配列またはエラーを返すが、それを型サインに明示的に記述していない場合。呼び出しコードでは戻り値が数値であることを期待し、result[1].toFixed(2]にアクセスすると、ランタイムエラーが発生します。

利点:

  • 型付けなしで簡単に書けるコード。

欠点:

  • ランタイム時にのみエラー。
  • 厳格な型付けの喪失、バグの増加。

ポジティブケース

関数がResult<T>の型で厳密に型付けされたタプルを返し、結果の処理が分解と最初の要素(エラー/ null)の明示的なランタイムチェックに基づいています。コンパイラーは、成功した型の絞り込みが行われる場合にのみ戻り値にアクセスできることを保証します。

利点:

  • コードの予測可能性と明確さ。
  • タプルに対する自動補完。

欠点:

  • 手動で型を記述する必要がある。
  • タイプガードやチェックの数が増える。