reactjs 在React中使用TypeScript输入动态标签?

bvhaajcl  于 2023-05-06  发布在  React
关注(0)|答案(6)|浏览(171)

如何在React中使用TypeScript输入动态标签?给定此代码:

interface CompProps {
  tag: string;
}

const MyComponent: React.FunctionComponent<CompProps> = ({
  tag = "div",
  children
}) => {
  const Wrapper = tag;

  return <Wrapper>{children}</Wrapper>;
};

我得到这个错误:
类型“{ children:“ReactNode; }”与类型“IntrinsicAttributes”没有共同属性。中文(简体)
在我看来,我必须添加适当的类型,但我不能弄清楚是哪个。

g52tjvyc

g52tjvyc1#

您可以传入一个string作为标记名并使用它,但是您需要正确地键入它以进行类型检查。tag应该是JSX.IntrinsicElements的键。

interface CompProps {
  tag: keyof JSX.IntrinsicElements;
}

const MyComponent: React.FunctionComponent<CompProps & React.HTMLAttributes<HTMLOrSVGElement>> = ({
  tag: Wrapper = "div",
  children,
  ...rest
}) => {
  return <Wrapper {...rest}>{children}</Wrapper>;
};

Playground链接

ippsafx7

ippsafx72#

所有HTML元素使用类型定义

为了允许所有HTML元素都用作标记,您可以利用在JSX命名空间中定义的IntrinsicElements接口的键。IntrinsicElements似乎包含HTML元素标记到其各自属性的Map(包括元素特定属性)。要使用这些密钥,我们可以执行以下操作:

interface Props {
  tag?: keyof JSX.IntrinsicElements
}

如果我想允许React组件作为标签,该怎么办?

React定义了两个接口:ComponentClassFunctionComponent。React还定义了这两个接口的联合,允许您指定任何React组件:ComponentType。我们可以创建一个这个和我们最后一个定义的联合,以允许组件和HTML标记。

import { ComponentType } from 'react';

interface Props {
  tag?: ComponentType | keyof JSX.IntrinsicElements;
}

好了,现在我有了标签,那么HTML属性呢?

如果您希望允许所有其他HTML属性,您可以扩展React.HTMLAttributes<Element>以获取所有共享的HTML属性(没有特定于元素的属性),或者您可以引入一个泛型并使用JSX.IntrinsicElements
第二种选择更为复杂,并带有一些警告。**您必须使用type而不是interface**来扩展/交叉您的PropsJSX.IntrinsicElements中的键上定义的特定属性。你还需要在你的函数上使用泛型,这样你就可以将它们传递给你的Props类型,这意味着你不能再使用React.FunctionComponent<Props>,因为这发生在访问任何泛型之前。这意味着您需要将children添加到Props定义中。
这是一个很大的问题,我认为用这个例子可以更好地解释:

// Define our Props type to allow the specifying of a Tag for HTML attributes
// Also define children as React does with React.ReactNode
type Props<Tag extends keyof JSX.IntrinsicElements> = {
  tag?: ComponentType | keyof JSX.IntrinsicElements;
  children?: ReactNode;
} & JSX.IntrinsicElements[Tag];

// Define our generic (Tag) again here and give it our default value
// Don't forget to specify the type Props<Tag> at the end of your function's arguments
// Then we can spread all props to the tag/Wrapper
function MyComponent<Tag extends keyof JSX.IntrinsicElements = 'div'>({ tag: Wrapper = 'div', ...props }: Props<Tag>) {
  return <Wrapper {...props} />;
}

// Example usage, noValidate is typed as
// (JSX attribute) React.FormHTMLAttributes<HTMLFormElement>.noValidate?: boolean | undefined
<MyComponent<'form'> tag="form" noValidate>
  {/* My Form Stuff */}
</MyComponent>;

// You don't need to specify 'div' since it is the default
<MyComponent id="page">
  <p>Just a paragraph inside of a regular div</p>
</MyComponent>;
f4t66c6m

f4t66c6m3#

我有一个类似的问题,我试图生成一个动态的标题标记的基础上通过'水平' prop 。它还生成了**“属性X在类型IntrinsicAttributes上不存在”**错误。
生成错误的代码如下:

// Heading.tsx
import React, { FunctionComponent, ReactNode } from 'react';

interface PropsType {
  level: 1 | 2 | 3 | 5 | 6;
  children?: ReactNode;
}

type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';

const HeadingComponent: FunctionComponent = ({
  level,
  children = null
}: PropsType) => {
  const Tag = `h${level}` as HeadingTag;
  return (
    <Tag>
      {children}
    </Tag>
  );
};

export default HeadingComponent;

// And I used this component all over my codebase like this;
// HomePage.tsx
<Heading level={1}>
  This Is A Title
</Heading>

我通过改变来解决这个问题:

const HeadingComponent: FunctionComponent = ({
  ... // removed for brevity
}: PropsType) => {
  ... // removed for brevity
};

致:

const HeadingComponent: FunctionComponent<PropsType> = ({
  ... // removed for brevity
}) => { 
  ... // removed for brevity
};
wfveoks0

wfveoks04#

const YourComponent: React.FC<Props> = ({ tag: Tag = 'button', children, ...props }) => (
  <Tag {...props}>
    {children}
  </Tag>
);
type Props = {
  tag?: keyof JSX.IntrinsicElements;
} & React.HTMLAttributes<HTMLOrSVGElement>;

这对我来说很好。

m2xkgtsf

m2xkgtsf5#

使用TypeScript在React中使用动态标签名的简单方法:

export default function Text(props: TextProps) {
  const { text, header } = props;

  let Tag: string;
  if (!header) Tag = "span"; 
  else Tag = `h${header}`;

  const ConstTag = Tag as "span" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6";

  return <ConstTag>{text}</ConstTag>;
}
vfwfrxfs

vfwfrxfs6#

我在这里看不到任何完全正确的答案。所有的答案都不会根据所使用的标签改变 prop 。
下面是我在工作中使用的组件类型的简化版本,它基于其标记更改 prop ,包括默认标记。如果您将错误的props放到所使用的标记中,例如尝试将formAction prop放到div标记中,也会出现错误。

import type { HTMLAttributes, ComponentPropsWithoutRef } from 'react';

// All valid HTML tags like 'div' | 'form' | 'a' | ...
type ValidTags = keyof JSX.IntrinsicElements;

// Generic type to generate HTML props based on its tag
type CustomTagProps<T extends ValidTags> = {
  tag?: T | ValidTags;
} & (ComponentPropsWithoutRef<T> & HTMLAttributes<HTMLOrSVGElement>);

/**
 * Make the default tag a constant to make it easy to infer both the default
 * generic parameter and the `tag` prop
 */
const DEFAULT_TAG = 'div' as const;

// Use the default `div` tag for both the generic parameter and `tag` prop
export function CustomTag<T extends ValidTags = typeof DEFAULT_TAG>({
  tag = DEFAULT_TAG,
  ...rest
}: CustomTagProps<T>): JSX.Element {
  /**
   * Assign the `tag` prop to a variable `CustomTag` of type ValidTags.
   *
   * The reason for doing this instead of rendering the `<Tag />` right away
   * is that the TypeScript compiler will yell at you with:
   * `Expression produces a union type that is too complex to represent`
   */
  const CustomTag: ValidTags = tag;

  // Render the custom tag with its props
  return <CustomTag {...rest}>This is a custom {tag} tag!</CustomTag>;
}

让我知道如果错过了什么,或者如果有一个改进,我可以做。

相关问题