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

时间:2019-05-03 12:23:56

标签: reactjs typescript

如何在带有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 个答案:

答案 0 :(得分:5)

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

为了允许将所有 HTML 元素用作您的标签,您可以利用 IntrinsicElements 命名空间中定义的 JSX 接口的键。 IntrinsicElements 似乎包含 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 属性(没有特定于元素的属性),或者您可以引入一个泛型并利用 {{1} }}。

第二个选项更复杂,并带有一些注意事项。 您必须使用 JSX.IntrinsicElements 而不是 type 来扩展/交叉您的 interface 和在 Props 中的键上定义的特定属性。您还需要在您的函数上使用泛型,以便您可以将它们传递给您的 JSX.IntrinsicElements 类型,这意味着您不能再使用 Props,因为这发生在访问任何泛型之前。这意味着您需要将 React.FunctionComponent<Props> 添加到您的 children 定义中。

我认为用这个例子更好地解释了很多词:

Props

答案 1 :(得分:1)

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

interface CompProps {
  tag: keyof JSX.IntrinsicElements;
}

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

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

您会注意到以下类型的断言:tag as 'div'。如果您注释掉类型断言,则需要这样做的原因将变得显而易见。从理论上讲,代码应该起作用。实际上,tsserver将使用超过2GB的内存或RAM挂起。它可能正在尝试检查所有可能的标签,并且由于某种原因挂起了该标签,我认为存在与此相关的GitHub问题(我正在考虑的问题已关闭,打开了一个{{3} }。断言技巧将使其仅检查足够接近的div

仅在3.4及更高版本上才出现此性能问题。在3.3中,它无需声明即可工作。

答案 2 :(得分:1)

我有一个类似的问题,我尝试根据传递的“关卡”道具生成动态标题标签。还会生成“属性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
};

答案 3 :(得分:0)