web-dev-qa-db-ja.com

Typescript-ユニオンタイプと交差タイプを組み合わせる方法

次のコンポーネントがあります。

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のボタン属性が使用されます(上記の例ではhrefbutton要素では許可されていないため、エラーが発生するはずです)。 aまたはbを使用しますが、一緒に使用することはできません-したがって、交差タイプです。

ここで何が問題になっていますか?また、aまたはbプロパティを追加したときに型が期待どおりに機能するのはなぜですか?

更新

いつエラーが発生し、いつコンパイルする必要があるかを示すために、例付きのプレイグラウンドを追加しました。

遊び場

5
leepowell

通常、私は次の手法を使用して一般的な機能コンポーネントを作成しますが、それはあなたのケースでも機能します。トリックは、コンポーネントをfunctionではなくconstとして宣言して、汎用的にできるようにすることです。これにより、プロップを一般的にすることができ、次のことが可能になります。

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>>であるとは言えないこと、その小道具と戻り値の型のみを入力できることです。

遊び場はこちら を確認してください(例は一番下に残しました)

[〜#〜] edit [〜#〜]質問の例を使用してコードを更新し、遊び場のリンクを修正しました

0