[typescript] typescript 인터페이스에는 두 가지 속성 중 하나가 있어야합니다.

나는 가질 수있는 인터페이스를 만들려고합니다

export interface MenuItem {
  title: string;
  component?: any;
  click?: any;
  icon: string;
}
  1. 요구 component하거나 click설정할 방법이 있습니까?
  2. 두 속성을 모두 설정할 수 없도록 요구하는 방법이 있습니까?



답변

유형이 조건부 논리가없고 서로 의존 할 수 없기 때문에 단일 인터페이스가 아니라 인터페이스를 분할하여 사용할 수 있습니다.

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;


답변

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]

그리고 단 하나만 제공되도록 요구하는 부분적이지만 절대적인 방법은 다음과 같습니다.

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놀이터 링크 끝에서 놓칠 수 있는 방법의 예를 제공했습니다 .

다음 예제를 사용하여 작동 방식에 대한 간략한 개요 :

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]에서 RequireAtLeastOne이된다

    {
        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}
    


답변

복잡한 조건부 유형 을 사용할 필요가 없습니다 . 더 간단하게 만들 수 있습니다.

  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: {} } // ✔

조합 유형은 ( |) 포괄적으로 대응 OR. 그것은되는 교차 비 조건부 속성.

  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 are set

기본적으로 둘 중 하나 component 또는 click 설정할 수 있으며 다른 하나는 동시에 추가 해서는 안됩니다 . TS는 할 수 있습니다 차별 노동 조합 유형을 밖으로 MenuItemXor에있는 해당한다, XOR.

참고 :이 XOR조건 MenuItemXor수락 된 답변으로 는 불가능합니다 .


함께 놀아 볼 코드 샘플


답변

여러 인터페이스가없는 대안은 다음과 같습니다.

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 MenuItem {
  title: string;
  icon: string;
}

export interface MenuItemComponent extends MenuItem{
  component: any;
}

export interface MenuItemClick extends MenuItem{
  click: any;
}

그런 다음 사용했습니다.

 appMenuItems: Array<MenuItemComponent|MenuItemClick>;

그러나 단일 인터페이스로 모델링하는 방법이 있기를 바랐습니다.


답변

Pick이러한 종류의 조건부 요구 사항을 설정하기 위해 모든 속성을 포함하는 기본 유형과 함께 사용하는 것을 좋아 합니다.

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

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

이것은 깨끗하고 유연합니다. 선언을 간단하고 읽기 쉽게 유지하면서 “모든 속성,이 두 속성 만 필요 또는이 속성 하나만 필요”등과 같은 것을 주장하면서 요구 사항에 대해 임의로 복잡해질 수 있습니다.


답변

나는 이것을 사용한다 :

type RequireField<T, K extends keyof T> = T & Required<Pick<T, K>>

용법:

let a : RequireField<TypeA, "fieldA" | "fieldB">;

이 수 fieldAfieldB 필요 .