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