Typescript 复杂类型推断

Typescript complex type inference

我想为 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

如何获取条件 3.If 中的代码提示 我没有手动添加泛型,条件 3 在 vscode 中不起作用。我的类型推断方式有误吗?

一般来说,如果您希望编译器推断类型参数 X,您应该给它一个类型 X 的值,以从中推断它。在您的 Rule 函数中,您试图从 X | BaseOption 类型的 options 值推断出 X extends Validator,但结果并不理想。一旦 options 可分配给 BaseOption,编译器就放弃更具体地推断 X,并退回到 Validator,导致 rule2rule3CombineOptions<Validator> 类型,这是正确的,但不够具体,无法满足您的需求。

如果我们重构 X 本身是扩展 Validator | BaseOption 的类型并且 options 属于 X 类型,那么推理将正常工作并且编译器不会忘记options:

的具体属性
function Rule<X extends Validator | BaseOption>(options: X): RuleOutput<X> {
    return {} as any // impl
}

现在的问题是弄清楚如何将输出类型 RuleOutput<X> 表示为输入类型 X 的函数。通过阅读您的 RequiredFuncCombineOptions 类型,看起来您想要做的是或多或少地输出 X as-is,除非有一个名为 [=36] 的键=],您想将 属性 类型更改为 (message: string) => void。这可以通过 mapped type with a conditoinal type 来表示,以检查密钥为 required:

的条件
type RuleOutput<X> = {
    [K in keyof X]:
    K extends "required" ? (message: string) => void : X[K]
}

让我们看看它是否有效:

// 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;
} */

看起来不错。您的 rule1 与以前的类型相同,现在 rule2rule3 更具体并且知道作为 options.

传入的特定键

Playground link to code