I'd like to do a more detailed type inference for Rule3
export interface BaseOption {
required?:true;
}
export type Validator = {
[key in string]?: (
val: number,
formData: string,
callback,
props
) => void | Promise<void | Error>;
};
export interface RequiredFunc {
required: (message: string) => void;
}
export type CombineOptions<ExtraOption extends Validator> = ExtraOption &
RequiredFunc;
function Rule<ExtraOption extends Validator>(
options: ExtraOption | BaseOption
) {
return {} as ExtraOption extends BaseOption
? CombineOptions<ExtraOption>
: ExtraOption;
}
// Condition1
const rule1 = Rule({
a() {},
});
rule1.a; // There are code hints in vscode
// Condition2
const rule2 = Rule({ required: true });
rule2.required; // There are code hints in vscode
// Condition3
const rule3 = Rule({
a(f, b) {},
required: true,
});
rule3.required; // There are code hints in vscode
rule3.a; // There are no code hints in vscode
How do I get the code hint for a in condition 3.If I do not add generics manually, the condition 3 is not worked in vscode. Is my type inference the wrong way?
Generally speaking if you want the compiler to infer a type parameter X
, you should give it a value of type X
from which to infer it. In your Rule
function you were trying to infer X extends Validator
from an options
value of type X | BaseOption
X | BaseOption
, and the results were not great. As soon as options
was assignable to BaseOption
, the compiler gave up with inferring X
more specifically and it fell back to Validator
, causing both rule2
and rule3
to be of type CombineOptions<Validator>
, which is true but not specific enough for your needs.
If we refactor so that X
is itself a type that extends Validator | BaseOption
Validator | BaseOption
and that options
is of type X
, then inference will work correctly and the compiler will not forget the specific properties of options
:
function Rule<X extends Validator | BaseOption>(options: X): RuleOutput<X> {
return {} as any // impl
}
The issue now is to figure out how to express the output type RuleOutput<X>
as a function of the input type X
. From reading your RequiredFunc
and CombineOptions
types, it looks like what you want to do is output X
more or less as-is, except if there is a key named required
, you want to change that property type to be (message: string) => void
. That can be expressed via a mapped type with a conditoinal type to check for the condition where the key is required
:
type RuleOutput<X> = {
[K in keyof X]:
K extends "required" ? (message: string) => void : X[K]
}
Let's see if it works:
// Condition1
const rule1 = Rule({
a() { },
});
/* const rule1: {
a: () => void;
} */
// Condition2
const rule2 = Rule({ required: true });
/* const rule2: {
required: (message: string) => void;
} */
// Condition3
const rule3 = Rule({
a(f, b) { },
required: true,
});
/* const rule3: {
a: (f: number, b: string) => void;
required: (message: string) => void;
} */
Looks good. Your rule1
is the same type as before, and now rule2
and rule3
are more specific and know about the specific keys passed in as options
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.