Issue
type Options<
TData,
TMethod,
TComputed extends Record<string, (data: TData) => any>
> = {
computed?: TComputed;
data?: TData;
methods?: TMethod
} &
ThisType<{
data: TData & {
[K in keyof TComputed]: ReturnType<TComputed[K]>
}
} & TMethod>
function Card<
TData extends Record<string, any> = {},
TMethod extends Record<string, Function> = {},
TComputed extends Record<string, (data: TData) => any> = {}
>(options: Options<TData, TMethod, TComputed>) {
}
Card({
data: {
a: 1
},
computed: {
c(data) { // got type error, the param data type can't be inferred.
return data.a + 1;
}
},
methods: {
bar() {
this.data.c
},
say() {
this.bar();
}
}
})
I wrote this piece of TypeScript code and got a type error, I wander why the function in computed options can't get the correct parameter type: Parameter 'data' implicitly has an 'any' type. (7006)
Solution
The generic type parameter TComputed
is constrained to Record<string, (data: TData) => any>
, and that's the only place where TComputed
depends on TData
.
But TypeScript does not use generic constraints as sources for type inference (see microsoft/TypeScript#7234). Constraints mostly serve to check a type argument that has been inferred. So TComputed
will be inferred from the computed
property of the input without any contribution from TData
.
That means there's no available context from which TypeScript can infer the type of the data
callback parameter, and so it falls back to any
which is an error under the --noImplicitAny
compiler option.
So that's why it's happening.
To get this working I'd suggest that you refactor so that the type of the computed
properly explicitly depends on TData
directly and not simply through a constraint. You can change the call signature so that computed
is of a mapped type over a type parameter R
corresponding to the return values of each property of computed
. Like this:
type Options<
TData,
TMethod,
R extends object
> = {
computed?: { [K in keyof R]: { (data: TData): R[K] } };
data?: TData;
methods?: TMethod
} &
ThisType<{
data: TData & R
} & TMethod>
function Card<
TData extends Record<string, any> = {},
TMethod extends Record<string, Function> = {},
R extends object = {}
>(options: Options<TData, TMethod, R>) { }
Now the type of computed
directly depends on TData
in the proper position for contextual typing, but the individual return type for each method is captured in R
. Note how the R
type is actually the underlying type you care about, and thus the type expression for Options
is a bit simpler (you don't need to try to use the utility type ReturnType
to tease out the information).
Okay, let's test it:
Card({
data: {
a: 1
},
computed: {
c(data) { // okay
return data.a + 1;
}
},
methods: {
bar() {
this.data.c
},
say() {
this.bar();
}
}
})
Looks good. TData
is inferred as { a: number; }
, TMethod
is inferred as { bar(): void; say(): void;}
, and R
is inferred as { c: number; }
, as desired.
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.