LoginSignup
1
0

More than 1 year has passed since last update.

readonlyの配列でincludesやindexOfをするとneverになってどうしようもなくなる

Last updated at Posted at 2023-01-07

コードがいろいろうまく行かなかったのでこのstackoverflowにたどり着きました。

問題

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);
                                 ^

というコードがあります。このincludes(5)でエラーが出ます。

Argument of type 'number' is not assignable to parameter of type 'never'

ここでのincludesの定義はこうです。

(method) ReadonlyArray.includes(searchElement: never, fromIndex: number | undefined): boolean

見事に引数がnever型になっていますね。
次にこのコードを見てください。1行変えただけです。

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [5, 6, 7, 8] //5を足しただけ
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);

コンパイルエラーは起きません。includesの型定義はこうです。引数を5のみ受け付けています。

(method) ReadonlyArray.includes(searchElement: 5, fromIndex: number | undefined): boolean

[2, 3, 4, 5]と[5, 6, 7, 8]の両方に共通しているリテラルだけ抜き出しているのです。

Q. どうするか?

A. どうしようもない
https://github.com/microsoft/TypeScript/issues/14520

暫定策

as constを外す

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
};

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);

こうすることで引数がnumberになります。

(method) Array.includes(searchElement: number, fromIndex?: number | undefined): boolean

as const number[]をつける

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5] as const number[]
  },
  area2: {
    adjacencies: [6, 7, 8] as const number[]
  }
};

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(5);

こうすることで引数がreadonly number[]になります。

anyにキャストする

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

(areas[area].adjacencies as any).includes(5);

万能の方法です。一時しのぎだけならこれで十分。

unknownにキャストしていい感じの型にキャストする

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

(areas[area].adjacencies as unknown as ElementUnion[]).includes(5);

なんとなく一番いいのはこれな気がします。

1
0
2

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
1
0