【问题标题】:Typing a dynamic tag in React with TypeScript?使用 TypeScript 在 React 中键入动态标签?
【发布时间】:2019-09-21 23:57:10
【问题描述】:

如何使用 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)

在我看来,我必须添加正确的类型,但我不知道是哪个。

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    您可以传入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 Link

    【讨论】:

    • keyof JSX.IntrinsicElements 仅适用于 html 元素。自定义反应组件呢
    【解决方案2】:

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

    为了允许所有 HTML 元素用作您的标签,您可以使用在 JSX 命名空间中定义的 IntrinsicElements 接口的键。 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&lt;Element&gt; 以获取所有共享的 HTML 属性(没有特定于元素的属性),或者您可以引入通用并利用 JSX.IntrinsicElements

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

    【讨论】:

    • 它对其他人有用吗?将道具传递给 Wrapper 时出现此 TS 错误:Type 'Omit, "tag">' is notassignable to type 'IntrinsicAttributes & SVGProps & ClassAttributes
    • 很棒的答案!我认为不可能像这样键入道具。这实现了一种输入 HTMLElements 的通用方式。干得好!
    【解决方案3】:

    我遇到了类似的问题,我尝试根据传递的“级别”道具生成动态标题标签。它还生成了 "Property X does not exist on type 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
    };
    

    【讨论】:

    • 现在也可以使用const Tag = `h${level}` as const,打字稿会得到正确的类型。
    【解决方案4】:
    const YourComponent: React.FC<Props> = ({ tag: Tag = 'button', children, ...props }) => (
      <Tag {...props}>
        {children}
      </Tag>
    );
    type Props = {
      tag?: keyof JSX.IntrinsicElements;
    } & React.HTMLAttributes<HTMLOrSVGElement>;
    

    这对我很有效。

    【讨论】:

      猜你喜欢
      • 2020-09-22
      • 2020-02-09
      • 1970-01-01
      • 2018-05-26
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      • 2019-01-22
      • 2017-08-10
      相关资源
      最近更新 更多