【问题标题】:React component type variance and assignabilityReact 组件类型差异和可分配性
【发布时间】:2019-03-18 18:34:44
【问题描述】:

上下文

我有两个组件。一个组件的 props 继承自另一个组件的 props。

declare namespace Props {
  interface Fruit {
    price: number;
  }

  interface Banana extends Fruit {
    curvature: number;
  }
}

declare const Fruit: React.FC<Props.Fruit>;
declare const Banana: React.FC<Props.Banana>;

事实是:

  • React.FC 被 React 定义为 props 的一个函数。
  • 我们知道函数类型的返回类型是协变的,而参数类型是逆变的。 Source

问题

现在,当尝试在 TypeScript 3.4.0-rc 中将一个分配给另一个时,我们得到:

/**
 * ✅ Compile-time error. Function arguments are contravariant.
 */
const fruit: typeof Fruit = Banana;

/**
 * ✅ No error. Function arguments are contravariant.
 */
const banana: typeof Banana = Fruit;

/**
 * ✅ No errors as expected.
 */
const one: React.ReactComponentElement<typeof Fruit> = <Fruit price={3} />;

/**
 * ⁉️ No errors (but expected one). Now it's covariant.
 */
const two: React.ReactComponentElement<typeof Fruit> = <Banana price={3} curvature={15} />

/**
 * ⁉️ No errors (but expected one). Now it's contravariant.
 */
const three: React.ReactComponentElement<typeof Banana> = <Fruit price={3} />

/**
 * ✅ No errors as expected.
 */
const four: React.ReactComponentElement<typeof Banana> = <Banana price={3} curvature={15} />

问题

为什么twothree 不会导致错误?

【问题讨论】:

  • 我认为根本问题是&lt;Banana price={3} curvature={15} /&gt; 的类型是什么。据我所知,类型始终是JSX.Element,即React.ReactElement&lt;any, any&gt;。因此,虽然存在表示类型化 jsx 元素的类型,但编译器似乎未使用它,但我认为编译器只是返回 JSX.Element 而无需费心做任何其他事情..
  • 以及相关的 GitHub 票证:github.com/Microsoft/TypeScript/issues/14729
  • 谢谢@TitianCernicova-Dragomir。我想知道这是否是理想的行为——这证明了这个故事还有更多内容。
  • 我认为这是实施的行为。我提到的问题确实表明团队愿意对此进行改进。

标签: reactjs typescript


【解决方案1】:

避免使用 JSX 并使用 React.createElement 解决了这个问题。

declare namespace Props {
  interface Fruit {
    price: number;
  }

  interface Banana extends Fruit {
    curvature: number;
  }
}

declare const Fruit: React.FC<Props.Fruit>;
declare const Banana: React.FC<Props.Banana>;

// ✅ 
const one: React.ReactComponentElement<typeof Fruit> = React.createElement(
  Fruit,
  { price: 1000 }
);

// ✅
const two: React.ReactComponentElement<typeof Banana> = React.createElement(
  Banana,
  { price: 1000, curvature: 12 }
);

/**
 * ✅ Compile-time error.
 * Type 'FunctionComponentElement<Banana>' is not assignable to type
 * 'ReactComponentElement<FunctionComponent<Fruit>, Pick<PropsWithChildren<Fruit>, "price" | "children">>'.
 * ...
 * fruits.tsx(10, 5): 'curvature' is declared here.
 */
const three: React.ReactComponentElement<typeof Fruit> = React.createElement(
  Banana
);

/**
 * ✅ Compile-time error.
 * Type 'FunctionComponentElement<Fruit>' is not assignable to type
 * 'ReactComponentElement<FunctionComponent<Banana>,
 * ...
 * Types of property 'propTypes' are incompatible. ?
 */
const four: React.ReactComponentElement<typeof Banana> = React.createElement(
  Fruit,
  { price: 12 }
);

但它会在 VSCode 中的 props 上创建另一个 - we lose autocomplete

【讨论】:

  • 您能否就当前键入 JSX 的最佳实践提供任何指导?我假设我们想继续使用 jsx,但是……也许不会?
  • @DevinGRhode 首先,我需要注意 JSX 类型在提出这个问题后发生了一些变化(库可以导出 namespace JSX 并回答您的问题:我不想阻止任何人使用JSX。它是用于 UI 组合的最佳 DSL 之一,我每天都在使用它。坦率地说,我从来不需要 React.ReactComponentElement&lt;T&gt;
【解决方案2】:

不是答案,但这可能对某些方法有用。 (我不想在我的git stash 中迷失的正在进行的工作)

// "React Create Element"
type RCE<
  HTMLNodeType extends HTMLElement = HTMLElement
> = React.DetailedReactHTMLElement<
  React.HTMLAttributes<HTMLNodeType>,
  HTMLNodeType
>
type RCE_string<
  HTMLNodeType extends 'div' ? HTMLDivElement : HTMLElement
> = React.DetailedReactHTMLElement<
  React.HTMLAttributes<HTMLNodeType>,
  HTMLNodeType
>
const t: RCE<HTMLDivElement> = <div />
const d: RCE<'div'> = <div />

const elementTypeMap ={
    Anchor: HTMLAnchorElement,
    Area: HTMLAreaElement,
    Audio: HTMLAudioElement,
    Base: HTMLBaseElement,
    Body: HTMLBodyElement,
    BR: HTMLBRElement,
    Button: HTMLButtonElement,
    Canvas: HTMLCanvasElement,
    Data: HTMLDataElement,
    DataList: HTMLDataListElement,
    Dialog: HTMLDialogElement,
    Div: HTMLDivElement,
    DList: HTMLDListElement,
    Embed: HTMLEmbedElement,
    FieldSet: HTMLFieldSetElement,
    Form: HTMLFormElement,
    Heading: HTMLHeadingElement,
    Head: HTMLHeadElement,
    HR: HTMLHRElement,
    Html: HTMLHtmlElement,
    IFrame: HTMLIFrameElement,
    Image: HTMLImageElement,
    Input: HTMLInputElement,
    Mod: HTMLModElement,
    Label: HTMLLabelElement,
    Legend: HTMLLegendElement,
    LI: HTMLLIElement,
    Link: HTMLLinkElement,
    Map: HTMLMapElement,
    Meta: HTMLMetaElement,
    Object: HTMLObjectElement,
    OList: HTMLOListElement,
    OptGroup: HTMLOptGroupElement,
    Option: HTMLOptionElement,
    Paragraph: HTMLParagraphElement,
    Param: HTMLParamElement,
    Pre: HTMLPreElement,
    Progress: HTMLProgressElement,
    Quote: HTMLQuoteElement,
    Slot: HTMLSlotElement,
    Script: HTMLScriptElement,
    Select: HTMLSelectElement,
    Source: HTMLSourceElement,
    Span: HTMLSpanElement,
    Style: HTMLStyleElement,
    Table: HTMLTableElement,
    TableCol: HTMLTableColElement,
    TableDataCell: HTMLTableDataCellElement,
    TableHeaderCell: HTMLTableHeaderCellElement,
    TableRow: HTMLTableRowElement,
    TableSection: HTMLTableSectionElement,
    Template: HTMLTemplateElement,
    TextArea: HTMLTextAreaElement,
    Title: HTMLTitleElement,
    Track: HTMLTrackElement,
    UList: HTMLUListElement,
    Video: HTMLVideoElement,
    WebView: HTMLWebViewElement
}

【讨论】:

    猜你喜欢
    • 2023-03-04
    • 2022-08-23
    • 2020-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-17
    • 2021-09-06
    相关资源
    最近更新 更多