打字稿接口需要存在两个属性之一

2024-01-31

我正在尝试创建一个可以具有以下功能的界面

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. 有没有办法要求component or click要设置
  2. 有没有办法要求这两个属性都不能设置?

在的帮助下Excludetype 是在 TypeScript 2.8 中添加的,提供了一种需要至少一组属性中的一个的通用方法:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>> 
    & {
        [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
    }[Keys]

要求提供一个且仅一个的部分但不是绝对的方法是:

type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
    Pick<T, Exclude<keyof T, Keys>>
    & {
        [K in Keys]-?:
            Required<Pick<T, K>>
            & Partial<Record<Exclude<Keys, K>, undefined>>
    }[Keys]

这是一个 TypeScript Playground 链接,显示了两者的实际效果。 https://www.typescriptlang.org/play/#src=type%20RequireAtLeastOne%3CT%2C%20Keys%20extends%20keyof%20T%20%3D%20keyof%20T%3E%20%3D%0D%0A%20%20%20%20Pick%3CT%2C%20Exclude%3Ckeyof%20T%2C%20Keys%3E%3E%0D%0A%20%20%20%20%26%20%7B%0D%0A%20%20%20%20%20%20%20%20%5BK%20in%20Keys%5D-%3F%3A%20Required%3CPick%3CT%2C%20K%3E%3E%20%26%20Partial%3CPick%3CT%2C%20Exclude%3CKeys%2C%20K%3E%3E%3E%0D%0A%20%20%20%20%7D%5BKeys%5D%0D%0A%0D%0Ainterface%20MenuItem%20%7B%0D%0A%20%20title%3A%20string%3B%0D%0A%20%20component%3F%3A%20number%3B%0D%0A%20%20click%3F%3A%20number%3B%0D%0A%20%20icon%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Atype%20ClickOrComponent%20%3D%20RequireAtLeastOne%3CMenuItem%2C%20'click'%20%7C%20'component'%3E%0D%0A%0D%0Aconst%20withComponent%3A%20ClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20component%3A%2052%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0Aconst%20withClick%3A%20ClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%2054%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0Aconst%20errorWithNeither%3A%20ClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0Aconst%20noErrorWithBoth%3A%20ClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%2054%2C%0D%0A%20%20%20%20component%3A%2024%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0Aconst%20errorWithBothWhenOneHasWrongType%3A%20ClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%2054%2C%0D%0A%20%20%20%20component%3A%20%22should%20be%20number%20here%22%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0A%0D%0Atype%20RequireOnlyOne%3CT%2C%20Keys%20extends%20keyof%20T%20%3D%20keyof%20T%3E%20%3D%0D%0A%20%20%20%20Pick%3CT%2C%20Exclude%3Ckeyof%20T%2C%20Keys%3E%3E%0D%0A%20%20%20%20%26%20%7B%20%5BK%20in%20Keys%5D-%3F%3A%0D%0A%20%20%20%20%20%20%20%20Required%3CPick%3CT%2C%20K%3E%3E%0D%0A%20%20%20%20%20%20%20%20%26%20Partial%3CRecord%3CExclude%3CKeys%2C%20K%3E%2C%20undefined%3E%3E%0D%0A%20%20%20%20%7D%5BKeys%5D%0D%0A%0D%0Atype%20OnlyOneClickOrComponent%20%3D%20RequireOnlyOne%3CMenuItem%2C%20'click'%20%7C%20'component'%3E%0D%0A%0D%0Aconst%20noErrorWithOnlyOne%3A%20OnlyOneClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%20534%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0Aconst%20errorWithBoth%3A%20OnlyOneClickOrComponent%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%20534%2C%0D%0A%20%20%20%20component%3A%2053%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0A%0D%0A%2F%2F%20This%20interface%20will%20be%20used%20to%20trick%20OnlyOneClickAtComponent%20into%20allowing%20an%20object%20with%20both%20%20%0D%0Ainterface%20ClickMenuItem%20%7B%0D%0A%20%20title%3A%20string%3B%0D%0A%20%20click%3A%20number%3B%0D%0A%20%20icon%3A%20string%3B%0D%0A%7D%0D%0A%0D%0Aconst%20hasBoth%20%3D%20%7B%0D%0A%20%20%20%20title%3A%20%22test%22%2C%0D%0A%20%20%20%20click%3A%2054%2C%0D%0A%20%20%20%20component%3A%2024%2C%0D%0A%20%20%20%20icon%3A%20%22icon%22%0D%0A%7D%0D%0A%0D%0A%2F%2F%20No%20error%20because%20excess%20properties%20are%20only%20disallowed%20when%20directly%20assigning%20an%20object%20literal%20%0D%0Aconst%20temp%3A%20ClickMenuItem%20%3D%20hasBoth%0D%0A%0D%0A%2F%2F%20No%20error%20despite%20temp%20actually%20having%20both%20because%20TS%20only%20knows%20that%20temp%20is%20a%20ClickMenuItem%20%0D%0Aconst%20trickIntoAllowingBoth%3A%20OnlyOneClickOrComponent%20%3D%20temp%0D%0A

警告与RequireOnlyOne问题是 TypeScript 并不总是在编译时知道运行时存在的每个属性。很明显RequireOnlyOne无法采取任何措施来阻止它不知道的额外属性。我提供了一个例子来说明如何RequireOnlyOne可能会错过游乐场链接末尾的东西。

使用以下示例快速概述其工作原理:

interface MenuItem {
  title: string;
  component?: number;
  click?: number;
  icon: string;
}

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
  1. Pick<T, Exclude<keyof T, Keys>> from RequireAtLeastOne变成{ title: string, icon: string},这是未包含在中的键的未更改属性'click' | 'component'

  2. { [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>> }[Keys] from RequireAtLeastOne becomes

    { 
        component: Required<{ component?: number }> & { click?: number }, 
        click: Required<{ click?: number }> & { component?: number } 
    }[Keys]
    

    这变成了

    {
        component: { component: number, click?: number },
        click: { click: number, component?: number }
    }['component' | 'click']
    

    最终变成

    {component: number, click?: number} | {click: number, component?: number}
    
  3. 上述步骤 1 和 2 的交集

    { title: string, icon: string} 
    & 
    ({component: number, click?: number} | {click: number, component?: number})
    

    简化为

    { title: string, icon: string, component: number, click?: number} 
    | { title: string, icon: string, click: number, component?: number}
    
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

打字稿接口需要存在两个属性之一 的相关文章

随机推荐