我正在尝试创造一个界面

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

是否有方法要求组件或单击进行设置 是否有一种方法要求两个属性都不能设置?


当前回答

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

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游乐场链接,展示了两者的作用。

RequireOnlyOne的警告是TypeScript在编译时并不总是知道运行时存在的每个属性。因此,显然RequireOnlyOne不能做任何事情来防止它不知道的额外属性。我提供了一个示例,说明RequireOnlyOne如何错过playground链接末尾的内容。

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

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

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>

Pick<T, Exclude<keyof T, Keys>> from RequireAtLeastOne becomes { title: string, icon: string}, which are the unchanged properties of the keys not included in 'click' | 'component' { [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] Which becomes { component: { component: number, click?: number }, click: { click: number, component?: number } }['component' | 'click'] Which finally becomes {component: number, click?: number} | {click: number, component?: number} The intersection of steps 1 and 2 above { title: string, icon: string} & ({component: number, click?: number} | {click: number, component?: number}) simplifies to { title: string, icon: string, component: number, click?: number} | { title: string, icon: string, click: number, component?: number}

其他回答

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

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

export interface MenuItem =
  Pick<MenuItemProps, "title" | "icon" | "component"> |
  Pick<MenuItemProps, "title" | "icon" | "click">

这样既干净又灵活。您可以任意地复杂化您的需求,断言诸如“需要所有属性,只需要这两个属性,或者只需要这一个属性”之类的东西,同时保持您的声明简单易读。

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

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游乐场链接,展示了两者的作用。

RequireOnlyOne的警告是TypeScript在编译时并不总是知道运行时存在的每个属性。因此,显然RequireOnlyOne不能做任何事情来防止它不知道的额外属性。我提供了一个示例,说明RequireOnlyOne如何错过playground链接末尾的内容。

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

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

type ClickOrComponent = RequireAtLeastOne<MenuItem, 'click' | 'component'>

Pick<T, Exclude<keyof T, Keys>> from RequireAtLeastOne becomes { title: string, icon: string}, which are the unchanged properties of the keys not included in 'click' | 'component' { [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] Which becomes { component: { component: number, click?: number }, click: { click: number, component?: number } }['component' | 'click'] Which finally becomes {component: number, click?: number} | {click: number, component?: number} The intersection of steps 1 and 2 above { title: string, icon: string} & ({component: number, click?: number} | {click: number, component?: number}) simplifies to { title: string, icon: string, component: number, click?: number} | { title: string, icon: string, click: number, component?: number}

还有另一个解决方案:

type RequiredKeys<T, K extends keyof T> = Required<Pick<T, K>> & Omit<T, K>;

type MenuItem2 = RequiredKeys<MenuItem, "component" | "click">;

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

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: "" }
];

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

上面的内容可以简化,但它可能更容易阅读,也可能不容易

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

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

不是用一个接口,因为类型没有条件逻辑,不能相互依赖,但你可以通过分离接口:

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;