簡體   English   中英

使用聯合類型 generics 時推斷單個類型(擴展反應組件道具)

[英]Inferring individual types when using union type generics (extending react component props)

我正在嘗試定義一個接受許多預定義屬性的包裝 React 組件。 屬性之一 ( tag ) 的存在和值還應確定是否有其他屬性可用。 react 組件將充當提供的tag組件的包裝器,該組件將使用包裝組件未專門處理的任何道具呈現。 如果省略tag ,包裝器仍然可以接受未處理的道具並將其轉發給子組件,但不提供任何類型(簡單的Record<string, any>或類似的東西就可以了)。

不幸的是,我還沒有找到一種方法來防止 Typescript 在省略tag道具時生成所有可能屬性的聯合。

Codesandbox上提供了該問題的最小復制。 我試圖解決的問題在其中得到了進一步的解釋,但是TL;DR:

<Field tag="textarea" name="test" cols={10} /> // cols is correctly typed
<Field tag="input" name="test" cols={10} /> // cols is correctly marked as invalid
<Field name="omitted" cols={10} /> // cols is typed here even though it's not a prop on <input>

代碼摘錄:

import * as React from "react";
import * as ReactDOM from "react-dom";

export type FieldTagType = React.ComponentType | "input" | "select" | "textarea";

type TagProps<Tag> = Tag extends undefined ? {}
  : Tag extends React.ComponentType<infer P> ? P
  : Tag extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[Tag]
  : never;

export type FieldProps<Tag extends FieldTagType> = {
  /** Field name */
  name: string;
  /** Element/component to createElement(), defaults to 'input' */
  tag?: Tag;
};

function Field<Tag extends FieldTagType>({
  name,
  tag,
  ...props
}: FieldProps<Tag> & TagProps<Tag>) {
  return React.createElement(tag || "input", {
    name,
    id: name + "-id",
    placeholder: name,
    ...props
  } as any);
}

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    {/*
      <textarea> supports the `cols` attribute, as expected.
    */}
    <Field tag="textarea" name="textarea" cols={10} />
    {/*
      <input> doesn't support the `cols` attribute, and
      since `tag` is properly set typescript will warn about it.
    */}
    <Field tag="input" name="test" cols={10} />
    {/*
      When `tag` is omitted it appears that FieldTagType
      is expanded into every possible type, rather than falling
      back to not providing any additional properties.
      This results in `cols` being included in the list of defined props,
      even though it is not supported by the `input` being rendered as fallback.

      Is it possible to modify the typings of TagProps/FieldProps/Field
      so that omitting `tag` will not include any additional properties,
      but also not error?
      Eg. TagProps are expanded to `Record<string, any>`
    */}
    <Field name="omitted" cols={10} />
  </React.StrictMode>,
  rootElement
);

當您在其參數中調用沒有tag屬性的Field時,這不會導致編譯器推斷"input"甚至undefined Tag泛型參數。 由於tag是一個可選屬性,因此tag的未定義值可以應用於任何有效的Tag選擇。 例如,您可以將Tag指定為"textarea" ,但仍保留tag屬性:

Field<"textarea">({ name: "xyz", cols: 10 }); // no error

當然,這不太可能真正發生 無論如何,當您省略tag時,編譯器不知道Tag應該是什么,只是將其默認為它的約束FieldTagType

如果您希望它默認為"input" ,您可以設置一個通用參數 default

function Field<Tag extends FieldTagType = input>({
// default here ----------------------> ~~~~~~~ 
    name,
    tag,
    ...props
}: FieldProps<Tag> & TagProps<Tag>) {
    return React.createElement(tag || "input", {
        name,
        id: name + "-id",
        placeholder: name,
        ...props
    } as any);
}

這不會影響您對其他情況的推斷,但是當省略tag時,現在您將得到空的TagProps<undefined>用於props ,以及所需的錯誤:

    <Field name="omitted" cols={10} /> // error!
    // -----------------→ ~~~~
    // Property 'cols' does not exist on type
    // 'FieldProps<"input"> & TagProps<"input">'

Playground 代碼鏈接

暫無
暫無

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

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