38
18

More than 3 years have passed since last update.

TypeScript で any や as を使わなくても型チェックをすり抜けてしまう話(Optional Propertyの危険性)

Last updated at Posted at 2020-03-26

要約

作られた場所がはっきりしない値の Optional Property にアクセスするときは、 undefined でないことのチェックではなく、積極的な型チェックが必要。

実例

type A = {
  b: boolean;
  n: number;
  s: string;
}

type B = {
  b: boolean;
  n: number;
}

type C = {
  b: boolean;
  n: number;
  s?: number;
}

const a: A = { b: true, n: 10, s: 'text' }; // OK
const b: B = a; // A は {b: boolean, n: number} を満たすのでOK
const c: C = b; // おや……? TypeScript の様子が……

if (c.s !== undefined) { // number|undefined -> number に絞り込み
  console.log(c.s.toFixed(10)); // 実体は string なので実行時エラー
}

結論

Optional Property にアクセスするときは、消極的な non-undefined チェックによる絞り込みではなく、積極的な型チェックをしましょう。

const a: A = { b: true, n: 10, s: 'text' }; // OK
const b: B = a; // A は {b: boolean, n: number} を満たすのでOK
const c: C = b; // おや……? TypeScript の様子が……

if (typeof c.s === 'number') { // 積極的チェックで安心
  console.log(c.s.toFixed(10)); // 通らない
}

補足

オーバーロードも単なるアサーションなので気を付けましょう。

function foo(s: string): string;
function foo(n: number): number;
function foo(v: string | number): string | number {
  // オーバーロードの定義とは逆の型の値を返せちゃう。
  return typeof v === 'string' ? Number(v) : String(v);
}

console.log(foo(5).toFixed(10)); // 実行時エラー

実際に起こりそうなパターン

type A = {
  n: number;
  s: string;
};

const trans = (b: { n: number; s?: number }): void => {
  b.s = 5;
};
const trans2 = (a: { n: number }): void => {
  trans(a);
};

const a: A = { n: 5, s: 'text' };

trans2(a); // A の部分型を期待する関数にaを投げたら……

console.log(a.s.toUpperCase()); // 実行時エラー, 型が壊れていた
38
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
18