😛

TypeScriptでオブジェクトのリテラル型を扱う際の注意点を考察

2022/05/06に公開

TypeScriptでオブジェクトのリテラル型を扱う際の注意点を考察

オブジェクトのリテラル型の配列において各メソッドで型を絞れるか(Narrowing)を検証

まずはこんな型

type Action =
  | {
      type: "text";
      id: string;
      content: string;
    }
  | {
      type: "amount";
      id: string;
      content: number;
    }
  | {
      type: "route";
      id: string;
      path: string;
      blank: boolean;
    }

型を付けた配列を作成

const actions: Action[] = [
  {
    type: "text",
    id: "foooooooooo",
    content: "nob"
  },
  {
    type: "amount",
    id: "foooooooooo",
    content: 800
  },
  {
    type: "route",
    id: "foooooooooo",
    path: "/happy",
    blank: true
  }
];

いざ検証

/* find */
const routeAction = actions.find((action) => action.type === "route");
console.log(routeAction?.path); // NG

/* filter */
const routeActions = actions.filter((action) => action.type === "route");
console.log(routeActions[0]?.path); // NG

/* forEach */
actions.forEach((action) => {
  if (action.type === "route") {
    console.log(action.path); // OK
  }
});

/* map */
actions.map((action) => {
  if (action.type === "route") {
    console.log(action.path); // OK
  }
});

NGのものは

Property 'path' does not exist on type 'Action'. Property 'path' does not exist on type '{ type: "text"; id: string; content: string; }'.

という型エラーになっています.これはそれぞれpathプロパティがない場合があるよと言っています.

つまり型を

{
  type: "route",
  id: "foooooooooo",
  path: "/happy",
  blank: true
}

に絞り込むことができていないようです.

.filterで絞り込めないのは結構きついものがある.

一応アノテーションで解決するとこうなります.

/* find */
const routeAction = actions.find(
  (action) => action.type === "route"
) as Extract<Action, { type: "route" }> | undefined;
console.log(routeAction?.path);
/* filter */
const routeActions2 = actions.filter(
  (action) => action.type === "route"
) as Extract<Action, { type: "route" }>[];
console.log(routeActions2[0].path);

うーん.

Discussion