Open4

TypeScriptの挙動メモ

moshmosh

ネストしてユニオンを持つ型にはnarrowingが働かない

type StringValue = {
  type: 'string';
};

type NumberValue = {
  type: 'number';
};

type OuterUnion = StringValue | NumberValue;

type InnerUnion = {
  type: 'string' | 'number';
};

const useOuterUnion = (union: OuterUnion) => {
  if (union.type === 'string') {
    // unionはStringUnionにnarrowingされるので型エラーは発生しない
    useString(union);
  }
};

const useInnerUnion = (union: InnerUnion) => {
  if (union.type === 'string') {
    // union.typeはstring"にnarrowingされる
    // unionはInnerUnionのままなので型エラーが発生する
    useString(union);
  }
};

/** 引数がStringValue型でないとき型エラーが発生する */
const useString = (string: StringValue) => {};
moshmosh

変数を書き換えられないときはnarrowingした型でキャプチャされる

let a: 'string' | 'number' = 'string';
if (a === 'string') {
  setTimeout(() => {
    // aは'string' | 'number'型
    a
  }, 1000);
}
// このようにコールバックの実行前にif文のチェック内容を壊される可能性があるので
// narrowingが発生していないと思われる
a = 'number';

const b: 'string' | 'number' = 'string';
if (b === 'string') {
  setTimeout(() => {
    // bは'string'型
    b
  }, 1000);
}
moshmosh

readonlyフィールドはimmutableではないのでフィールドのnarrowingが引き継がれない

const obj: { value: 'string' | 'number' } = { value: 'string' };

const obj2: { readonly value: 'string' | 'number' } = obj;

if (obj2.value === 'string') {
  // obj2.valueは'string'型
  setTimeout(() => {
    // obj2.valueは'string' | 'number'型
  }, 1000);
}
// readonlyなフィールドも書き換えられる可能性がある
obj.value = 'number';