简体   繁体   中英

Typescript complex type inference

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 | BaseOptionValidator | 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 .

Playground link to code

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM