背景

假设我有一个包含所有可选值的接口:

interface MyInterface
{
    property1?: string
    property2?: string
    proeprty3?: string
}

当使用MyInterface作为类型时,我可以使用以下实用程序类型指定至少需要它的一个属性:

type AtLeastOnePropRequired<T> = { [P in keyof T]-?: Record<P, T[P]> }[keyof T]
type CustomRequired<T> = T & AtLeastOnePropRequired<T>

使用示例:

// At least one of `property1`, `property2`, or `property3` is required.
CustomRequired<MyInterface>

这有点类似于Required,只是实用程序类型会使所有属性都是强制的,而不仅仅是一个.

顺便说一句,这里有另一种实现AtLeastOnePropRequired的方法:

type AtLeastOnePropRequired<T> = T & { [P in keyof T]: Required<Pick<T, P>> }[keyof T]

问题

假设我想扩展MyInterface并添加一个新的属性,它也是可选的:

interface NewInterface extends MyInterface
{
    property4?: boolean
}

这里的皱纹是,我不能再以同样的方式使用CustomRequired.如果我使用CustomRequired<NewInterface>,它的意思是:

property1property2property3property4中的至少一个 是必需的.

我需要的是一个打字,意思是:

至少需要property1property2property3中的一个. property4是可选的,但如果提供,则不满足前面的 要求.

请参见下面的"结论"部分,了解这可能是什么样子的用法示例.

我试着调整CustomRequired度来处理这种情况.


示例

下面是一个不应满足类型要求的示例:

// Does not contain one of `property1`, `property2`, or `property3`.
{
    property4: false
}

下面是一些示例,说明了什么应该满足类型要求:

// Does contain one of `property1`, `property2`, or `property3`.
{
    property1: "hello world!",
    property3: "goodbye world!"
}

// Does contain one of `property1`, `property2`, or `property3`.
{
    property2: "hello again!",
    property4: false
}

结论

如果不是扩展MyInterface,而是执行以下操作,就可以实现这种行为:

interface NewInterface
{
    myInterface: CustomRequired<MyInterface>
    property4?: boolean
}

现在,至少有一个属性MyInterface必须提供给NewInterfacemyInterface属性,但它很笨重,如果可能的话,我宁愿使用extend方法.

我想象它可能工作的方式是,除了将接口传递给实用程序类型CustomRequired之外,我还传递了不满足"至少一个"要求的属性名的联合.类似于OmitExclude的工作方式.

例如:

interface MyInterface
{
    property1?: string
    property2?: string
    proeprty3?: string
}

interface NewInterface extends MyInterface
{
    property4?: boolean
}

// At least one property of `NewInterface` is required, and any property will satisfy it.
// This is the current implementation.
CustomRequired<NewInterface>

// At least one property of `NewInterface` is required, but `property4` won't satisfy it.
CustomRequired<NewInterface, 'property4'>

// At least one property of `NewInterface` is required, but neither `property1` nor `property4` will satisfy it.
CustomRequired<NewInterface, 'property1' | 'property4'>

如果实用程序类型能够处理上面没有传递键的联合的情况,以及传递联合的情况,那就太好了.如果这是不可能的,那么有一个单独的实用类型将是可以的,只有当需要传递一个union时才使用,在其他情况下,任何属性将满足要求,我将使用我现有的实用类型.

我对TypeScript泛型还不够精通,无法马上弄清楚如何做到这一点.我认为这将涉及到将第二个泛型传递给CustomRequiredAtLeastOnePropRequired,然后对引用keyof T的部分进行调整,以确保它不是联合泛型的一部分.

推荐答案

我认为你可以这样写你想要的CustomRequired个实用程序类型:

type CustomRequired<T, K extends keyof T = never> = 
    T & AtLeastOnePropRequired<Omit<T, K>>

你只需从K输入Omit个键,然后把它传给AtLeastOnePropRequired. 请注意,如果您遗漏了K,如在CustomRequired<T>中,则使用neverdefault值,并且由于Omit<T, never> is essentially just T, it falls back to your original version of CustomRequired `.

我们来测试一下:

type MyType = CustomRequired<NewInterface, "property4">
let t: MyType;

// these are all accepted
t = { property1: "", property2: "", property3: "", property4: true };
t = { property1: "", property4: true };
t = { property2: "", property4: true };
t = { property3: "", property4: true };
t = { property1: "" };

// these are errors
t = {}; // error
t = { property4: true }; // error

看起来不错您必须至少具有NewInterface的属性之一,但property4不计算在内.

Playground link to code

Typescript相关问答推荐

如何将@for的结果绑定到变量?

具有条件通用props 的react 类型脚本组件错误地推断类型

类型脚本中的Gnomeshell 扩展.如何导入.ts文件

角形标题大小写所有单词除外

如何根据模板类型实现不同的模板函数行为?

对数组或REST参数中的多个函数的S参数进行类型判断

状态更新后未触发特定元素的Reaction CSS转换

部分类型化非 struct 化对象参数

泛型类型联合将参数转换为Never

Vue SFC中的多个脚本块共享导入的符号

为什么不能使用$EVENT接收字符串数组?

Angular 15使用react 式表单在数组内部创建动态表单数组

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

将接口映射到交叉口类型

为什么TS2339;TS2339;TS2339:类型A上不存在属性a?

递归生成常量树形 struct 的键控类型

是否可以将类型参数约束为不具有属性?

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

为什么 typescript 在对象合并期间无法判断无效键?