【TypeScript】複数のオブジェクトのユニオン型でいずれかのオブジェクトに存在しないプロパティを指定してもコンパイルエラーにならない方法

 TypeScript は緩やかにも厳格にも設定次第の強さで型を JavaScript に付与してくれる AltJS です。なるべく厳格にする方が安全なプログラミングができますが、厳格さを重視するあまり型と格闘する時間が開発すべきプログラムのための時間を大きく食う事態がしばしば起きそうになります。これは避けるべき事態で都度asanyを使うのがよくある方法なのですが、そもそも扱う型自体もほどほど緩くしたい時があります。これは例えば次です。

/**
 * 位置を表すマーカーを保存するプログラムを考えます。
 * それぞれのマーカーには位置情報とデザインの情報とセーブしない一時的な情報が含まれています。
 * セーブデータの型は全てのマーカーで共通にします。
 */
/** 図形マーカー。CSSを利用して色でマーカー表現する */
type ShapeMarker = {
  position: {
    x: number;
    y: number;
  };
  color: CSS.Properties['color'];
  fillColor: CSS.Properties['color'];
  notSaveSomeData: {
    /** 色々 */
  };
};
/** 画像マーカー。画像でマーカー表現する */
type ImgMarker = {
  position: {
    x: number;
    y: number;
  };
  imgUrl: string;
  notSaveSomeData: {
    /** 色々 */
  };
};
/** セーブデータ。上記マーカーの情報を保持するための型 */
type MarkerInSave = {
  position: {
    x: number;
    y: number;
  };
  imgUrl?: string;
  color?: CSS.Properties['color'];
  fillColor?: CSS.Properties['color'];
};
/** 保存処理 */
const saveMarker = (marker: ShapeMarker | ImgMarker) => {
  // 引数で受け取ったマーカーを元にセーブするためのデータのオブジェクトを作ろうとすると
  const saveData: MarkerInSave = {
    position: marker.position,
    // ここから↓の様なエラーが出る
    // TS2339: Property 'color' does not exist on type 'ShapeMarker | ImgMarker'.
    //   Property 'color' does not exist on type 'ImgMarker'.
    color: marker.color,
    fillColor: marker.fillColor,
    imgUrl: marker.imgUrl,
  };
};

 作ってる側としてはプロパティ未定義の方を参照しても undefined が返ってくるだけで、そのまま動いて欲しい気持ちになります。一応color: (marker as any).color,の様にas, anyを使えば動いてくれますが、各プロパティに対してこれを行うのは手間ですし、型の解決のためだけに余分に実行される処理を追加するのも望ましくありません。そこでユニオン型の部分をいじって、もっと緩い型になる方法を考えます。これは次でできます。

// 元々あったユニオン型に加えて、ユニオン型を構成する全ての型のプロパティについて undefined を用うる Partial<交差型>を用意する
const saveMarker = (marker: (ShapeMarker | ImgMarker) & Partial<ShapeMarker & ImgMarker>) => {
  const saveData: MarkerInSave = {
    position: marker.position,
    color: marker.color,
    fillColor: marker.fillColor,
    imgUrl: marker.imgUrl,
  };
};
const marker: ShapeMarker = {
  color: 'rgba(255,0,0,1)',
  fillColor: 'rgba(255,128,128,0.25)',
  notSaveSomeData: {},
  position: { x: 0, y: 0 },
};
saveMarker(marker);
// エラーなしで実行可能

 (ShapeMarker | ImgMarker) & Partial<ShapeMarker & ImgMarker>を日本語で言うと「ShapeMarker か ImgMarker の型を満たし、ShpaeMarkerと ImgMarker のどちらかもしくは両方にあるプロパティすべてを undefined で持ちうる型」です。より具体的に展開すると次です。

type ArgMarker =
  | {
      position: {
        x: number;
        y: number;
      };
      notSaveSomeData: {
        /** 色々 */
      };
      color: CSS.Properties['color'];
      fillColor: CSS.Properties['color'];
      imgUrl?: string;
    }
  | {
      position: {
        x: number;
        y: number;
      };
      notSaveSomeData: {
        /** 色々 */
      };
      color?: CSS.Properties['color'];
      fillColor?: CSS.Properties['color'];
      imgUrl: string;
    };

 TypeScript の発する”そのプロパティを持たない”という警告に対して、undefined の値で持ち得ると型で示しているわけです。これならば細かくプロパティ毎に定義を追加する手間を省け、余分に型を緩くして不具合を埋め込むリスクも避けられます。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG