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

IT技术 reactjs typescript
2021-05-02 05:49:30

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

interface CompProps {
  tag: string;
}

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

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

我收到此错误:

输入'{孩子:ReactNode; }' 与类型 'IntrinsicAttributes' 没有共同的属性。ts(2559)

在我看来,我必须添加正确的类型,但我无法弄清楚是哪个。

4个回答

您可以传入 astring作为标记名称并按原样使用它,但您需要正确键入它才能使类型检查工作。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>;
};

游乐场链接

对所有 HTML 元素使用类型定义

为了允许将所有 HTML 元素用作您的标签,您可以利用命名空间中IntrinsicElements定义接口的键JSXIntrinsicElements似乎包含 HTML 元素标签到它们各自属性的映射(包括元素特定的属性)。要使用这些键,我们可以执行以下操作:

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>;

我有一个类似的问题,我试图根据传递的“级别”props生成动态标题标签。它还生成了“类型 IntrinsicAttributes 上不存在属性 X”的错误。

产生错误的代码如下;

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

这对我很有效。