I want to search an element inside an readonly array union
const areas = {
area1: {
adjacencies: [2, 3, 4, 5]
},
area2: {
adjacencies: [6, 7, 8]
}
} as const;
let area: keyof typeof areas;
if (Math.random() < 0.5) {
area = "area1";
} else {
area = "area2"
}
// Argument of type 'number' is not assignable to parameter of type 'never'
areas[area].adjacencies.includes(3);
I also tried indexOf
, but it didn't work either. And I found the includes
type is ReadonlyArray<T>.includes(searchElement: never, fromIndex: number | undefined): boolean
.
I suppose the includes
type should be the union of the elements of two readonly array, just like below:
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(3);
How can I apply ElementUnion
to includes
or indexOf
??
Here is the playground
CodePudding user response:
When you do as const
you tell typescript "even if encounter a literal like 0 or "hello", rather than inferring it as a general number or string, infer it literally".
So unless both of the arrays has the element you search for, it wont work.
const areas = {
area1: {
adjacencies: [2, 3, 4, 5]
},
area2: {
adjacencies: [6, 7, 8, 3]
}
} 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(3);
Notice how I added a 3 to the second array.
Edit:
Look at the signature of the function, only the union of the arrays can be passed to it.
(method) ReadonlyArray<T>.includes(searchElement: 3, fromIndex: number | undefined): boolean
Edit 2:
Example:
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"
}
// uh oh, .adjacencies can be either one of the arrays
// if the variable was declared without as const, the function would accept a number
// however since that it not the case, typescript takes the intersection
// (elements present in both arrays) as literal arguments
areas[area].adjacencies.includes(3);
///////////////////////////////////////////////////////////////
if (area === 'area1') {
// here typescript knows what the elements are
// but since the variable was declared as const, only literal values
// that are in the array can be passed to the function
// v Hover over this
areas[area].adjacencies.includes(2);
} else {
// v Hover over this
areas[area].adjacencies.includes(6);
}
// Direct access
// v Hover over this
areas.area1.adjacencies.includes(2);
// v Hover over this
areas.area2.adjacencies.includes(6);
// v Hover over this
areas['area1'].adjacencies.includes(2)
// v Hover over this
areas['area2'].adjacencies.includes(6)
View in playground for hovers
CodePudding user response:
Thanks for @zerkms mentioning of https://stackoverflow.com/a/53035048/251311, there seems to be two workarounds.
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"
}
// first solution using type assertion
(areas[area].adjacencies as unknown as ElementUnion[]).includes(3); // works!
// second solution attempting using declaration merging
// from lib.es2016.array.include.d.ts, which I thick is quite ok
interface ReadonlyArray<T> {
includes<T>(searchElement: T, fromIndex?: number): boolean;
}
areas[area].adjacencies
is of type readonly [2, 3, 4, 5] | readonly [6, 7, 8]
, which should be equivalent of ReadonlyArray<2 | 3 | 4 | 5 | 6 | 7 | 8>
, and I made an experiment
const readonlyArr = [1, 2, 3] as const;
readonlyArr.includes(3); // typescript successfully inferred T is 1 | 2 | 3
It seems like typescript is not able to convert union of readonly array to union of array members, which may be on purpose or not