try 创建一个函数,其中(从类型的Angular 来看)基于前两个参数,它确定是否应该提供第三个参数.是否需要第三个参数取决于对象中是否存在特定的嵌套键(基于前两个参数的路径).我创造了一个人为的例子来演示:

Example

type ExampleType = {
  john: {
    toolbox: { color: string }
  },
  jane: {
    screwdriver: { color: string }
  },
  sally: {
    hammer: { color: string },
    nail: {}
  }
}

type ColorArgs<Color> = Color extends never ? { color?: Color } : { color: Color };

type ExampleFn = <
  Name extends Extract<keyof ExampleType, string>,
  Thing extends Extract<keyof ExampleType[Name], string>,
  Color extends (ExampleType[Name][Thing] extends { color: infer C } ? C : never)
>(config: { name: Name, thing: Thing } & ColorArgs<Color>) => string;

export const exampleFn: ExampleFn = ({ name, thing, color }) => {
  console.log(name, thing, color);
  return 'hello';
};

exampleFn({
  name: 'sally',
  thing: 'nail',
});

我希望函数能正常工作,并且当name是sally而thing是nail时不允许 colored颜色 参数.

推荐答案

首先,这个例子没有真正的理由,你需要在函数中有三个generic个类型参数.不要使用Color作为某个类型的类型参数constrained,你可以直接使用该类型:

type ExampleFn = <
  K1 extends keyof ExampleType,
  K2 extends keyof ExampleType[K1]      
>(config: { name: K1, thing: K2 } & ColorArgs<(
    ExampleType[K1][K2] extends { color: infer C } ? C : never
 )>) => string;

然后,我们需要修复复杂的ColorArgs<( ExampleType[K1][K2] extends { color: infer C } ? C : never )>,使其成为实际工作的东西.您的ColorArgs类型函数是distributive conditional type,而那些alwaysnever映射到never,以便与联合保持一致(参见Inferring nested value types with consideration for intermediate optional keys的答案以获取更多信息).由于你显然试图使用never作为一个符号来检测何时color不是输入的键,那么你得到的是never而不是你想要的类型.

因此,最好的方法是完全避免中间never,只是单独处理这两种情况. 我会这么做

type ExampleFn = <
  K1 extends keyof ExampleType,
  K2 extends keyof ExampleType[K1],
>(config:
  { name: K1, thing: K2, color?: unknown } &
  ColorArgs<ExampleType[K1][K2]>
) => string;

type ColorArgs<T> =
  "color" extends keyof T ? Pick<T, "color"> : { color?: never }

这里我把完整的ExampleType[K1][K2]传递给T,然后ColorArgs判断color是否是T的键.如果是,你得到Pick<T, "color">,给你T的部分,如果不是,你得到{color?: never}禁止这样的属性(它从技术上说使它成为never类型的optional property,这有点不同,但非常接近禁止).

我在config的类型中包含color?: unknown的原因只是为了确保编译器知道,无论K1K2是什么泛型类型,用color索引到config总是安全的.否则你会遇到一个问题:

export const exampleFn: ExampleFn = ({ name, thing, color }) => {
  console.log(name, thing, color);
  return 'hello';
};

destruct 的color财产不会被识别为有效的.

现在我们可以判断它是否按预期工作:

exampleFn({ name: 'sally', thing: 'nail' }); // okay
exampleFn({ name: 'sally', thing: 'nail', color: "red" }); // error
exampleFn({ name: "jane", thing: "screwdriver", color: "red" }); // okay
exampleFn({ name: "jane", thing: "screwdriver" }); // error

看起来不错

Playground link to code

Typescript相关问答推荐

如果在TypeScript中一个原始的类方法是递归的,如何使用类decorator 模式?

用泛型类型覆盖父类函数导致类型y中的Property x不能赋给基类型Parent中的相同属性."'''''' "

忽略和K的定义

有没有办法解决这个问题,类型和IntrinsicAttributes类型没有相同的属性?

为什么在回调函数的参数中不使用类型保护?

为什么我的一个合成函数不能推断类型?

诡异的文字类型--S为物语的关键

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

扩展参数必须具有元组类型或传递给REST参数

在打印脚本中使用泛型扩展抽象类

有没有可能创建一种类型,强制在TypeScrip中返回`tyPeof`语句?

Typescript -返回接口中函数的类型

如何避免从引用<;字符串&>到引用<;字符串|未定义&>;的不安全赋值?

NPM使用Vite Reaction应用程序运行预览有效,但我无法将其部署到Netlify

基于泛型的类型脚本修改类型

使用强制转换编写打字函数的惯用方法

如何在Type脚本中创建一个只接受两个对象之间具有相同值类型的键的接口?

如何使用angular15取消选中位于其他组件中的复选框

包含多个不同类构造函数的接口的正确类型?

TypeScript 中的通用函数包装和类型推断