簡體   English   中英

Typescript - 如何組合 Union 和 Intersection 類型

[英]Typescript - how to combine Union and Intersection types

我有以下組件:

export enum Tags {
  button = 'button',
  a = 'a',
  input = 'input',
}

type ButtonProps = {
  tag: Tags.button;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
  JSX.IntrinsicElements['button'];

type AnchorProps = {
  tag: Tags.a;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
  JSX.IntrinsicElements['a'];

type InputProps = {
  tag: Tags.input;
} & ({ a?: string; b?: undefined } | { a?: undefined; b?: string }) &
  JSX.IntrinsicElements['input'];

type Props = ButtonProps | AnchorProps | InputProps;

const Button: React.FC<Props> = ({ children, tag }) => {
  if (tag === Tags.button) {
    return <button>{children}</button>;
  }
  if (tag === Tags.a) {
    return <a href="#">{children}</a>;
  }
  if (tag === Tags.input) {
    return <input type="button" />;
  }
  return null;
};

// In this instance the `href` should create a TS error but doesn't...
<Button tag={Tags.button} href="#">Click me</Button>

// ... however this does
<Button tag={Tags.button} href="#" a="foo">Click me</Button>

這已經被剝離了一點,以便能夠提出這個問題。 關鍵是我正在嘗試一個有區別的聯合以及交叉類型。 我正在嘗試根據標簽值實現所需的道具。 因此,如果使用Tags.button則使用 JSX 的按鈕屬性(並且上面示例中的href應該會產生一個錯誤,因為它不允許在button元素上使用)-但是另一個復雜性是我希望使用ab ,但它們不能一起使用 - 因此是交集類型。

我在這里做錯了什么,為什么在添加ab屬性時該類型只能按預期工作?

更新

我添加了一個帶有示例的游樂場,以顯示它何時應該出錯以及何時應該編譯。

操場

在您的示例中,有兩個問題必須解決,並且都源於同一個“問題”(功能)。

在 Typescript 中,以下內容不起作用,正如我們有時想要的那樣:

interface A {
  a?: string;
}

interface B {
  b?: string;
}

const x: A|B = {a: 'a', b: 'b'}; //works

您想要的是從 A 中明確排除 B,從 B 中排除 A - 這樣它們就不能一起出現。

這個問題討論了類型的“異或”,並建議使用 package ts-xor ,或者自己編寫。 這是那里的答案示例(在 ts-xor 中使用相同的代碼):

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;

現在,有了這個,我們終於可以解決你的問題了:

interface A {
  a?: string;
}

interface B {
  b?: string;
}

interface C {
  c?: string;
}

type CombinationProps = XOR<XOR<A, B>, C>;

let c: CombinationProps;
c = {}
c = {a: 'a'}
c = {b: 'b'}
c = {c: 'c'}
c = {a: 'a', b: 'b'} // error
c = {b: 'b', c: 'c'} // error
c = {a: 'a', c: 'c'} // error
c = {a: 'a', b: 'b', c: 'c'} // error

更具體地說,您的類型將是:

interface A {a?: string;}
interface B {b?: string;}

type CombinationProps = XOR<A, B>;

type ButtonProps = {tag: Tags.button} & JSX.IntrinsicElements['button'];
type AnchorProps = {tag: Tags.a} & JSX.IntrinsicElements['a'];
type InputProps = {tag: Tags.input} & JSX.IntrinsicElements['input'];

type Props = CombinationProps & XOR<XOR<ButtonProps,AnchorProps>, InputProps>;

您必須使 a 和 b 不是可選的,而是使用對應於包含 OR 的聯合類型 (|)。 它與非條件屬性相交(從不)。 所以現在 ts 可以正確區分它們:更新

type ButtonProps = {
  tag: Tags.button;
  a?: string;
  b?: string
} & 
  JSX.IntrinsicElements['button'];

這里是一個游樂場

通常我使用以下技術來創建通用功能組件,但它也適用於您的情況。 訣竅是將組件聲明為function而不是const以便能夠使其通用。 這允許 props 是通用的,這反過來又允許您執行以下操作:

export enum Tags {
    button = 'button',
    a = 'a',
    input = 'input',
  }

  type Props<T extends Tags = Tags> = JSX.IntrinsicElements[T] & {
    tag: T;
  } & ({ a?: string; b?: never } | { a?: never; b?: string });

  function Button<T extends Tags>({ children, tag }: Props<T>) {
    if (tag === Tags.button) {
      return <button>{children}</button>;
    }
    if (tag === Tags.a) {
      return <a href="#">{children}</a>;
    }
    if (tag === Tags.input) {
      return <input type="button" />;
    }
    return null;
  }

  // These should error due to href not being allowed on a button element
  const a = <Button tag={Tags.button} href="#" a="foo">Click me</Button>
  const b = <Button tag={Tags.button} href="#">Click me</Button>

  // These should work
  const c = <Button tag={Tags.button} a="foo">Click me</Button>
  const d = <Button tag={Tags.button} b="foo">Click me</Button>
  const e = <Button tag={Tags.button}>Click me</Button>

  // This should error as `a` and `b` can't be used together
  const f = <Button tag={Tags.button} a="#" b='a'>Click me</Button>

唯一的缺點是不能直接鍵入Button組件,不能說function Button<T>React.FC<Props<T>> ,只能鍵入它的 props 和返回類型。

此處查看操場(我將您的示例留在了底部)

編輯我用您問題中的示例更新了代碼並修復了操場鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM