I am trying to solve code:
type Wrap<T> = {
value: T
}
function wrapIt<T>(t: T): Wrap<T> {
return { value: t}
}
const arr: Array<string|number> = ["a", 1]
const arr2: Array<Wrap<string|number>> = arr.map(wrapIt)
const arr3: Array<Wrap<string>|Wrap<number>> = arr.map(wrapIt) // <--- Doesn't compile
The compiler says:
Type 'Wrap<string | number>[]' is not assignable to type '(Wrap<string> | Wrap<number>)[]'.
Type 'Wrap<string | number>' is not assignable to type 'Wrap<string> | Wrap<number>'.
Type 'Wrap<string | number>' is not assignable to type 'Wrap<string>'.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
arr3 is what I need for being compatible with some other code I dont own. Also, conceptually, it seems more correct.
Is there some typescript black magic to make it behave the way I want?
For now I will type cast it using as.
EDIT: Array<string|number> was just used as an example, I am using this pattern quite often so I would like the solution to work for Array<T> where T is arbitrary and can be a union like string|number but not just.
CodePudding user response:
You can change the definition of the function to distribute over T when returning the type. So this means that when mapping a union (such as number | string) you will get a union of instantiations of Wrapped for each union constituent (so Wrapped<number> | Wrapped<string>) not an instantiation of Wrapped for the union (so not Wrapped<string | number>)
type DistraibutiveWrapped<T> = T extends T ? Wrap<T> : Wrap<T>
function wrapIt<T>(t: T): DistraibutiveWrapped<T> {
return { value: t} as DistraibutiveWrapped<T>
}
CodePudding user response:
Using explicit type checking?
type Wrap<T> = {
value: T
}
function wrapIt<T>(t: T): Wrap<T> {
return { value: t }
}
const arr: Array<string | number> = ["a", 1]
const arr2: Array<Wrap<string | number>> = arr.map(wrapIt)
const arr3: Array<Wrap<string> | Wrap<number>> = arr.map(x => {
switch (typeof x) {
case 'string':
return wrapIt<string>(x);
case 'number':
return wrapIt<number>(x);
default:
return wrapIt(x);
}
});
CodePudding user response:
You can create extra function and overload it:
type Wrap<T> = {
value: T
}
function wrapIt<T>(t: T): Wrap<T> {
return { value: t }
}
const arr: Array<string | number> = ["a", 1]
function map<Item,>(arr: Item[]): Array<Wrap<string> | Wrap<number>>
function map<Item,>(arr: Item[]) {
return arr.map(wrapIt)
}
// (Wrap<string> | Wrap<number>)[] which is equivalent to Array<Wrap<string> | Wrap<number>>, just different syntax
const result = map(['hello', 42])
