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.