Reactjs 使用联合类型泛型时推断单个类型(扩展react组件道具)

Reactjs 使用联合类型泛型时推断单个类型(扩展react组件道具),reactjs,typescript,typescript-generics,Reactjs,Typescript,Typescript Generics,我试图定义一个包装器React组件,它接受许多预定义的属性。其中一个属性(标记)的存在和值还应确定是否有其他属性可用。react组件将充当所提供的标记组件的包装器,该组件将使用包装组件未专门处理的任何道具进行渲染。如果省略了标记,包装器仍然可以接受未处理的道具并将其转发给子组件,但不提供任何类型(一个简单的记录或类似的记录就可以了) 不幸的是,我还没有找到一种方法来防止Typescript在省略标记时生成所有可能属性的并集 可在上获得该问题的最低复制版本。我试图解决的问题在书中有进一步的解释,但

我试图定义一个包装器React组件,它接受许多预定义的属性。其中一个属性(
标记
)的存在和值还应确定是否有其他属性可用。react组件将充当所提供的
标记
组件的包装器,该组件将使用包装组件未专门处理的任何道具进行渲染。如果省略了
标记
,包装器仍然可以接受未处理的道具并将其转发给子组件,但不提供任何类型(一个简单的
记录
或类似的记录就可以了)

不幸的是,我还没有找到一种方法来防止Typescript在省略
标记时生成所有可能属性的并集

可在上获得该问题的最低复制版本。我试图解决的问题在书中有进一步的解释,但是TL;医生:

<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
);
//cols键入正确
//cols被正确标记为无效
//cols在这里输入,即使它不是道具
代码摘录:

<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
);
import*as React from“React”;
从“react dom”导入*作为react dom;
导出类型FieldTagType=React.ComponentType |“输入”|“选择”|“文本区域”;
类型TagProps=标记扩展未定义?{}
:Tag.ComponentType?P
:标记JSX.intrinsiceelements的名称?JSX.intrinsiceelements[Tag]
:从不;
导出类型FieldProps={
/**字段名*/
名称:字符串;
/**元素/组件到createElement(),默认为“输入”*/
标签?:标签;
};
函数域({
名称
标签,
…道具
}:现场道具和标语道具){
返回React.createElement(标记| |“输入”{
名称
id:name+“-id”,
占位符:名称,
…道具
}(如有的话);
}
const rootElement=document.getElementById(“根”);
ReactDOM.render(
{/*
如预期的那样,支持'cols'属性。
*/}
{/*
不支持'cols'属性,并且
由于'tag'的设置正确,因此typescript将对此发出警告。
*/}
{/*
省略“tag”时,FieldTagType
扩展到所有可能的类型,而不是下降
返回到不提供任何附加属性。
这导致“cols”被包括在已定义的道具列表中,
即使“input”作为回退提供不支持它。
是否可以修改TagProps/FieldProps/Field的类型
因此省略“tag”将不包括任何附加属性,
但也不是错误吗?
TagProps扩展为“记录”`
*/}
,
根元素
);

当调用
字段时,其参数中没有
标记
属性,这不会导致编译器为
标记
泛型参数推断
输入
,甚至
未定义
。由于
tag
是可选属性,因此
tag
的未定义值可应用于任何有效的
tag
选择。例如,您可以将
Tag
指定为
“textarea”
,但仍保留
Tag
属性:

Field<"textarea">({ name: "xyz", cols: 10 }); // no error
这不会影响其他情况下的推断,但如果省略了
标记
,则现在您会得到
道具
的空
道具
,以及所需的错误:

    <Field name="omitted" cols={10} /> // error!
    // -----------------→ ~~~~
    // Property 'cols' does not exist on type
    // 'FieldProps<"input"> & TagProps<"input">'
//错误!
// -----------------→ ~~~~
//类型上不存在属性“cols”
//‘现场道具<输入>和标签道具<输入>’


也许可以尝试
函数字段
将默认标记类型设置为“input”@JacobSmit谢谢您的建议!谢谢你的评论,非常感谢!我完全忘了提到我曾尝试将
'input'
指定为通用参数默认值,正如您所写的那样,它确实解决了整个问题。唯一的缺点是,由于添加了大量输入属性,这使得在intellisense/LSP属性/属性完成列表(至少在我的vim设置中)中更难发现特定于组件的属性。我觉得这方面没有办法改进,所以除非有人有办法解决这个问题,否则我会将这个答案标记为正确答案。例如,如果需要提供一个
标签来包含我可以从
开始的附加属性,那么可能有更好的东西,但我不知道我是否能承诺花更多的时间来研究它。祝你好运