问题
I'm trying to transform some fields of my object by providing field names, currently I wrote something like follows:
interface Foo {
a: number[],
b: string[],
}
type Bar = { [T in keyof Foo] : (arg : Foo[T]) => Foo[T] }
function test<T extends keyof Foo>(field: T) {
const foo : Foo = {
a: [],
b: [],
};
const bar: Bar = {
a: arg => /* some code */ [],
b: arg => /* some code */ [],
};
foo[field] = bar[field](foo[field]);
}
But I end up with the following error message on bar[field](foo[field])
:
Argument of type 'Foo[T]' is not assignable to parameter of type 'number[] & string[]'.
Type 'number[] | string[]' is not assignable to type 'number[] & string[]'.
Type 'number[]' is not assignable to type 'number[] & string[]'.
Type 'number[]' is not assignable to type 'string[]'.
Type 'number' is not assignable to type 'string'.
Type 'Foo[T]' is not assignable to type 'number[]'.
Type 'number[] | string[]' is not assignable to type 'number[]'.
Type 'string[]' is not assignable to type 'number[]'.
Type 'string' is not assignable to type 'number'
But shouldn't typescript "know" that with the same T
, Foo[T]
and Parameters<Bar[T]>
should be the same?
回答1:
Perhaps the compiler should know that, but it doesn't. I tend to call this issue "correlated types" or "correlated records". The compiler sees foo[field]
and bar[field]
as union-typed things, which is true enough. But it treats their types as independent, meaning that as far as it knows, foo[field]
might be number[]
while bar[field]
might be a function that takes string[]
. It doesn't see that the type of foo[field]
is correlated to the type of bar[field]
in such a way that knowing one fixes the other. There's an open issue, microsoft/TypeScript#30581 (which I filed, fwiw) suggesting that there be some support for correlated types, but it's not clear if that's ever going to happen or how.
All we have right now are workarounds. The two workarounds mentioned in that issue: either use redundant code to force the compiler to walk through the different possibilities and guarantee type safety, or use type assertions to give up on some type safety but maintain brevity. For your code it would look like this:
// redundant code
const f: keyof Foo = field;
switch (f) {
case "a":
foo[f] = bar[f](foo[f]);
break;
case "b":
foo[f] = bar[f](foo[f]);
break;
}
// type assertion
foo[field] = (bar[field] as <T>(arg: T) => T)(foo[field]);
I usually opt for the type assertion. Okay, hope that helps; good luck!
Link to code
来源:https://stackoverflow.com/questions/59077562/type-of-properties-in-a-generic-function