typescript接口需要存在两个属性之一

Nix*_*Nix 48 typescript

我正在尝试创建一个可以拥有的界面

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
Run Code Online (Sandbox Code Playgroud)
  1. 有没有办法要求componentclick设置
  2. 有没有办法要求两个属性都不能设置?

KPD*_*KPD 81

借助ExcludeTypeScript 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]
Run Code Online (Sandbox Code Playgroud)

并且要求提供一个且仅提供一个的部分但非绝对的方式是:

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]
Run Code Online (Sandbox Code Playgroud)

这是一个TypeScript playground链接,显示两者都在运行.

需要注意的RequireOnlyOne是,TypeScript并不总是在编译时知道运行时将存在的每个属性.所以显然RequireOnlyOne无法做任何事情来防止它不知道的额外属性.我提供了一个例子,说明如何RequireOnlyOne在游乐场链接的末尾错过任何东西.

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

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

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>
Run Code Online (Sandbox Code Playgroud)
  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]来自RequireAtLeastOne成为

    { 
        component: Required<{ component?: number }> & { click?: number }, 
        click: Required<{ click?: number }> & { component?: number } 
    }[Keys]
    
    Run Code Online (Sandbox Code Playgroud)

    哪个成了

    {
        component: { component: number, click?: number },
        click: { click: number, component?: number }
    }['component' | 'click']
    
    Run Code Online (Sandbox Code Playgroud)

    最终变成了

    {component: number, click?: number} | {click: number, component?: number}
    
    Run Code Online (Sandbox Code Playgroud)
  3. 上面的1和2的交集

    { title: string, icon: string} 
    & 
    ({component: number, click?: number} | {click: number, component?: number})
    
    Run Code Online (Sandbox Code Playgroud)

    简化为

    { title: string, icon: string, component: number, click?: number} 
    | { title: string, icon: string, click: number, component?: number}
    
    Run Code Online (Sandbox Code Playgroud)

  • 从 TS 3.5 开始,我们可以使用 `Omit` 帮助器(`Pick` + `Exclude` 的快捷方式)使“至少一个”变得更短:`type RequireAtLeastOne&lt;T, R extends keyof T = keyof T&gt; =省略&lt;T, R&gt; &amp; { [ P in R ] :必需&lt;Pick&lt;T, P&gt;&gt; &amp; 部分&lt;Omit&lt;T, P&gt;&gt; }[R];`。基本框架是相同的,因此只需要注释(快速注意:在 `[K in keys]-?:` 中,可以安全地省略 `-?` 修饰符:两个版本中都保留了可选性) (11认同)
  • 谁能解释一下表达式“[K in keys]-”中的连字符(减号)的作用是什么? (5认同)
  • 非常感谢您提供描述性示例.真的提供信息. (3认同)
  • 感谢您提供信息丰富且格式良好的回复。我一直在玩这个例子,所以我可以更好地理解它。看来,如果您为“组件”和“单击”提供除“任何”以外的类型,则至少具有一组有效属性的对象将通过。我认为这是因为类型简化为 `{ title: string, icon: string, component: any} | { title: string, icon: string, click: any}` 其中声明类型是 3 个属性,而不是 4 个,其中一个是可选的。我试图找到有关在地图符号中使用数组的文档,但找不到。 (3认同)
  • @Lopsided 它删除了原始对象“T”中可能存在的“K”的任何可选修饰符。请参阅 /sf/ask/3475879361/ 至于其在“[K in Keys]-?:”中的具体用途:我刚刚做了一些测试,看起来实际上是这样不会对最终结果产生影响,但我将其放入只是为了确定“RequireAtLeastOne”的行为方式相同,无论为“Keys”指定的属性最初是否是可选的。 (3认同)
  • 注意:这是官方 Microsoft Azure Typescript SDK 的一部分 https://docs.microsoft.com/en-us/javascript/api/@azure/keyvault-certificates/requireatleastone?view=azure-node-latest (2认同)

for*_*d04 52

有一个更简单的解决方案。无需依赖any或复杂的条件类型 (见答案)

  1. 有没有办法要求设置组件或单击?(含OR)
type MenuItemOr = {
    title: string;
    icon: string;
} & ({ component: object } | { click: boolean }) 
// brackets are important here: "&" has precedence over "|"

let testOr: MenuItemOr;
testOr = { title: "t", icon: "i" } // error, none are set
testOr = { title: "t", icon: "i", component: {} } // ?
testOr = { title: "t", icon: "i", click: true } // ?
testOr = { title: "t", icon: "i", click: true, component: {} } // ?
Run Code Online (Sandbox Code Playgroud)

联合类型|)对应于包容OR。它与非条件属性相交

使用in运算符将值缩小回成分之一:

if ("click" in testOr) testOr.click // works 
Run Code Online (Sandbox Code Playgroud)
  1. 有没有办法要求不能设置这两个属性?(独家OR/ XOR)
type MenuItemXor = {
    title: string;
    icon: string;
} & (
        | { component: object; click?: never }
        | { component?: never; click: boolean }
    )

let testXor: MenuItemXor;
testXor = { title: "t", icon: "i" } // error, none are set
testXor = { title: "t", icon: "i", component: {} } // ?
testXor = { title: "t", icon: "i", click: true } // ?
testXor = { title: "t", icon: "i", click: true, component: {} } //error,both set
Run Code Online (Sandbox Code Playgroud)

基本上任一 component click可设定,其他的应该从不1被同时加入。TS可以做出区分联合类型出来的MenuItemXor,它对应于XOR

XOR对于MenuItemXor接受的答案,此条件是不可能的。


操场

1 从技术上讲,prop?: never解析为prop?: undefined,尽管前者经常用于说明。

  • 如何处理“testOr.click;”返回错误““MenuItemOr”类型上不存在属性“click”。”? (6认同)
  • @EduardoDallmann,您可以使用 [`in` 运算符](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-the-in-operator) 来检查对象([Playground](https://www.typescriptlang.org/play/#code/C4TwDgpgBAshB2BXAksCBbA8gJygXigG8BYAKCgqmAEtgAbCALigGdhtr4BzAbjMqjUAxgHt4zNh259SAXygAyKAApCUUejBiEwZknQAjCLnkAfIurrCA1swMiRAIbwo sgJRkyAEwhC6TtjQDMBUEGw4zHBIqBg4PFAA9IlQXNQAbgisIujQaBG46YHUTgYMUADutAAWsAgoaFi4oJBepNQAZioARP423YKu+CA47kT8lMM4AHR9QtZJKRUi2NYsExTJYQWzOVrwOotQxtgrZPJknT0a+zoD nNSj2GMk5JPhT7ua2vChW8urdZvTYpKbYWZWeZHE5nORAA)) (4认同)
  • 与这个答案相比,它很光滑,但在国际海事组织的可读性上略有损失:/sf/answers/4289727991/ (2认同)

ssu*_*ube 46

不使用单个接口,因为类型没有条件逻辑并且不能相互依赖,但您可以通过拆分接口:

export interface BaseMenuItem {
  title: string;
  icon: string;
}

export interface ComponentMenuItem extends BaseMenuItem {
  component: any;
}

export interface ClickMenuItem extends BaseMenuItem {
    click: any;
}

export type MenuItem = ComponentMenuItem | ClickMenuItem;
Run Code Online (Sandbox Code Playgroud)

  • @DanielRamos 你可以在`ComponentMenuItem` 上添加`click?: never`,在`ClickMenuItem` 上添加`component?: never`。 (7认同)
  • 有什么方法可以使其与参数解构兼容吗?如果我尝试 `function myFunc({ title, icon, component, click }: MenuItem)` 类型“MenuItem”上不存在属性“组件”,我会收到 TS 错误。“MenuItem”类型上不存在属性“click”。 (5认同)
  • 嗨,大家好,您是如何强迫您在其中一种界面之间进行选择的,而不是在两者之间进行选择?使用这种类型,同时具有component和click的对象不会出错。 (2认同)
  • 有一个来自 Sindresorhus 的库,其中包含一堆实用程序类型,称为 type-fest。链接[此处](https://github.com/sindresorhus/type-fest) (2认同)

Jua*_*des 10

没有多个接口的替代方法是

export type MenuItem = {
  title: string;
  component: any;
  icon: string;
} | {
  title: string;
  click: any;
  icon: string;
};

const item: MenuItem[] = [
  { title: "", icon: "", component: {} },
  { title: "", icon: "", click: "" },
  // Shouldn't this error out because it's passing a property that is not defined
  { title: "", icon: "", click: "", component: {} },
  // Does error out :)
  { title: "", icon: "" }
];
Run Code Online (Sandbox Code Playgroud)

我在如何创建需要设置单个属性的Partial-like中问过类似的问题

上面的内容可以简化,但可能会或可能不容易阅读

export type MenuItem = {
  title: string;
  icon: string;
} & (
 {component: any} | {click: string}
)
Run Code Online (Sandbox Code Playgroud)

请注意,这些都不阻止您同时添加两者,因为TypeScript确实允许使用AND / OR的对象具有额外的属性,请参见https://github.com/Microsoft/TypeScript/issues/15447


Nix*_*Nix 7

我最终做了:

export interface MenuItem {
  title: string;
  icon: string;
}

export interface MenuItemComponent extends MenuItem{
  component: any;
}

export interface MenuItemClick extends MenuItem{
  click: any;
}
Run Code Online (Sandbox Code Playgroud)

然后我用:

 appMenuItems: Array<MenuItemComponent|MenuItemClick>;
Run Code Online (Sandbox Code Playgroud)

但是希望有一种方法可以使用单个界面对其进行建模。

  • 实际上微软建议它应该是:`&lt;MenuItemComponent|MenuItemClick&gt;[]` (2认同)

Dan*_*ane 7

我喜欢Pick与包含所有属性的基本类型一起使用来建立这些类型的条件要求。

interface MenuItemProps {
  title: string;
  component: any;
  click: any;
  icon: string;
}

export type MenuItem =
  Pick<MenuItemProps, "title" | "icon" | "component"> |
  Pick<MenuItemProps, "title" | "icon" | "click">
Run Code Online (Sandbox Code Playgroud)

这既干净又灵活。您可以使您的需求变得任意复杂,断言诸如“需要所有属性,仅需要这两个属性,或仅需要这一个属性”等内容,同时保持声明简单且可读。

  • 接口不能“=”类型。如果这是“export interface MenuItem = ...”,那么它将是有效的。 (2认同)
  • @J'e我认为你的意思是“导出类型MenuItem = ...” (2认同)

dmw*_*268 6

这是实现其中一个但不能同时实现两者的简单方法

type MenuItem =  {
  title: string;
  component: any;
  click?: never;
  icon: string;
} | {
  title: string;
  component?: never;
  click: any;
  icon: string;
}

// good
const menuItemWithComponent: MenuItem = {
  title: 'title',
  component: "my component",
  icon: "icon"
}

// good
const menuItemWithClick: MenuItem = {
  title: 'title',
  click: "my click",
  icon: "icon"
}

// compile error
const menuItemWithBoth: MenuItem = {
  title: 'title',
  click: "my click",
  component: "my click",
  icon: "icon"
}
Run Code Online (Sandbox Code Playgroud)


Lin*_*uel 5

我用这个:

type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>
Run Code Online (Sandbox Code Playgroud)

用法:

let a : RequireField<TypeA, "fieldA" | "fieldB">;
Run Code Online (Sandbox Code Playgroud)

这使得fieldAfieldB 要求

  • 我必须稍微调整一下解决方案才能使其在这里工作:`type RequireField&lt;T, K extends keyof T&gt; = T | 必需&lt;选择&lt;T, K&gt;&gt;`。我将交集运算符 (`&amp;`) 更改为并集运算符 (`|`)。 (2认同)