TypeScript詳解
本書をサポートしてください:購入 または 寄付
(広告、ブロックしないでください。)

21 型アサーション(キャスト関連)



この章では、TypeScriptの型アサーションについて説明します。これは、他の言語の型キャストに関連しており、as演算子を使用して実行されます。

21.1 型アサーション

型アサーションを使用すると、TypeScriptが値に対して計算した静的型をオーバーライドできます。これは、型システムの制限を回避するために役立ちます。

型アサーションは他の言語の型キャストに関連していますが、例外をスローせず、ランタイムでは何も実行しません(静的にいくつかの最小限のチェックを実行します)。

const data: object = ['a', 'b', 'c']; // (A)

// @ts-expect-error: Property 'length' does not exist on type 'object'.
data.length; // (B)

assert.equal(
  (data as Array<string>).length, 3); // (C)

コメント

型アサーションは最後の手段であり、できる限り避けるべきです。これにより、静的型システムが通常提供するセーフティネットが(一時的に)削除されます。

A行でも、TypeScriptの静的型をオーバーライドしました。しかし、それは型アノテーションによって行いました。このオーバーライド方法は、型アサーションよりもはるかに安全です。なぜなら、TypeScriptの型はアノテーションの型に代入可能であるという制約がはるかに厳しいためです。

21.1.1 型アサーションの代替構文

TypeScriptには、型アサーションの代替となる「山括弧」構文があります。

<Array<string>>data

この構文は避けることをお勧めします。それは時代遅れになりつつあり、React JSXコード(.tsxファイル内)と互換性がありません。

21.1.2 例:インターフェースのアサーション

任意のオブジェクトobj.nameプロパティにアクセスするには、objの静的型を一時的にNamedに変更します(A行とB行)。

interface Named {
  name: string;
}
function getName(obj: object): string {
  if (typeof (obj as Named).name === 'string') { // (A)
    return (obj as Named).name; // (B)
  }
  return '(Unnamed)';
}

21.1.3 例:インデックスシグネチャのアサーション

次のコード(A行)では、as Dict型アサーションを使用することで、推論された型がobjectである値のプロパティにアクセスできます。つまり、静的型objectを静的型Dictでオーバーライドしています。

type Dict = {[k:string]: any};

function getPropertyValue(dict: unknown, key: string): any {
  if (typeof dict === 'object' && dict !== null && key in dict) {
    // %inferred-type: object
    dict;

    // @ts-expect-error: Element implicitly has an 'any' type because
    // expression of type 'string' can't be used to index type '{}'.
    // [...]
    dict[key];
    
    return (dict as Dict)[key]; // (A)
  } else {
    throw new Error();
  }
}

21.2.1 非ナルアサーション演算子(後置!

値の型がundefinedまたはnullを含むユニオン型の場合、非ナルアサーション演算子(または非ヌルアサーション演算子)は、これらの型をユニオンから削除します。TypeScriptに「この値はundefinedまたはnullになりません」と伝えています。その結果、これらの2つの値の型によって妨げられていた操作を実行できます。たとえば

const theName = 'Jane' as (null | string);

// @ts-expect-error: Object is possibly 'null'.
theName.length;

assert.equal(
  theName!.length, 4); // OK
21.2.1.1 例 – マップ:.has()の後で.get()

Mapメソッド.has()を使用すると、Mapに特定のキーがあることがわかります。しかし、.get()の結果はその知識を反映していないため、非ナルアサーション演算子を使用する必要があります。

function getLength(strMap: Map<string, string>, key: string): number {
  if (strMap.has(key)) {
    // We are sure x is not undefined:
    const value = strMap.get(key)!; // (A)
    return value.length;
  }
  return -1;
}

Mapの値がundefinedになることがない場合は、非ナルアサーション演算子を回避できます。その場合、.get()の結果がundefinedかどうかをチェックすることで、欠損エントリを検出できます。

function getLength(strMap: Map<string, string>, key: string): number {
  // %inferred-type: string | undefined
  const value = strMap.get(key);
  if (value === undefined) { // (A)
    return -1;
  }

  // %inferred-type: string
  value;

  return value.length;
}

21.2.2 確定代入アサーション

厳格なプロパティ初期化がオンになっている場合、TypeScriptが考えていない場合でも、特定のプロパティを初期化していることをTypeScriptに伝える必要がある場合があります。

これは、TypeScriptが不必要にエラーを報告する例です。

class Point1 {
  // @ts-expect-error: Property 'x' has no initializer and is not definitely
  // assigned in the constructor.
  x: number;

  // @ts-expect-error: Property 'y' has no initializer and is not definitely
  // assigned in the constructor.
  y: number;

  constructor() {
    this.initProperties();
  }
  initProperties() {
    this.x = 0;
    this.y = 0;
  }
}

A行とB行で確定代入アサーション(感嘆符)を使用すると、エラーがなくなります。

class Point2 {
  x!: number; // (A)
  y!: number; // (B)
  constructor() {
    this.initProperties();
  }
  initProperties() {
    this.x = 0;
    this.y = 0;
  }
}