[英]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
元素上使用)-但是另一個復雜性是我希望使用a
或b
,但它們不能一起使用 - 因此是交集類型。
我在這里做錯了什么,為什么在添加a
或b
屬性時該類型只能按預期工作?
我添加了一個帶有示例的游樂場,以顯示它何時應該出錯以及何時應該編譯。
在您的示例中,有兩個問題必須解決,並且都源於同一個“問題”(功能)。
在 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.