【问题标题】:Dynamic tag name in React JSXReact JSX 中的动态标签名称
【发布时间】:2020-09-11 23:10:10
【问题描述】:

我正在尝试为 HTML 标题标签(h1h2h3 等)编写一个 React 组件,其中标题级别是通过属性指定的。

我试着这样做:

<h{this.props.level}>Hello</h{this.props.level}>

预期的输出如下:

<h1>Hello</h1>

但这不起作用。有什么可行的方法吗?

【问题讨论】:

标签: reactjs jsx


【解决方案1】:

没办法就地做,把它放在一个变量中(with first letter capitalised):

const CustomTag = `h${this.props.level}`;

<CustomTag>Hello</CustomTag>

【讨论】:

  • 绝对比React.createClass容易​​,我更喜欢这种方式。谢谢。
  • @zerkms 你知道如何向 CustomTag 添加属性吗?谢谢
  • @Sabrina &lt;CustomTag foo="bar"&gt;
  • 我猜这是因为第一个大写字母 it gets interpreted as a React component 和 html 标签名称也被认为是有效的 React 组件,所以它可以工作。看起来有点可疑,但我会接受它。
  • 如果组件存储在对象的属性中,则不需要大写首字母。 var foo = { bar: CustomTag }; return &lt;foo.bar /&gt; 工作正常。
【解决方案2】:

为了完整起见,如果要使用动态名称,也可以直接调用React.createElement,而不是使用JSX:

React.createElement(`h${this.props.level}`, null, 'Hello')

这避免了必须创建新的变量或组件。

带道具:

React.createElement(
  `h${this.props.level}`,
  {
    foo: 'bar',
  },
  'Hello'
)

来自docs

创建并返回给定类型的新 React 元素。 type 参数可以是标签名称字符串(例如 'div''span'),也可以是 React 组件类型(类或函数)。

使用 JSX 编写的代码将转换为使用 React.createElement()。如果您使用 JSX,通常不会直接调用 React.createElement()。请参阅React Without JSX 了解更多信息。

【讨论】:

    【解决方案3】:

    在动态标题 (h1, h2...) 的实例中,组件可以像这样返回React.createElement(上面提到的Felix)。

    const Heading = ({level, children, ...props}) => {
        return React.createElement(`h${level}`, props , children)
    }
    

    为了可组合性,props 和 children 都被传递。

    See Example

    【讨论】:

      【解决方案4】:

      所有其他答案都可以正常工作,但我会添加一些额外的,因为这样做:

      1. 它更安全一些。即使您的类型检查失败,您仍然 返回一个适当的组件。
      2. 它更具声明性。通过查看此组件的任何人都可以看到它可以返回什么。
      3. 它更灵活,例如代替“h1”、“h2”……对于标题类型,您可以使用其他一些抽象概念“sm”、“lg”或“主要”、“次要”

      标题组件:

      import React from 'react';
      
      const elements = {
        h1: 'h1',
        h2: 'h2',
        h3: 'h3',
        h4: 'h4',
        h5: 'h5',
        h6: 'h6',
      };
      
      function Heading({ type, children, ...props }) {    
        return React.createElement(
          elements[type] || elements.h1, 
          props, 
          children
        );
      }
      
      Heading.defaultProps = {
        type: 'h1',
      };
      
      export default Heading;
      

      你可以像这样使用它

      <Heading type="h1">Some Heading</Heading>
      

      或者你可以有一个不同的抽象概念,例如你可以定义一个尺寸道具,如:

      import React from 'react';
      
      const elements = {
        xl: 'h1',
        lg: 'h2',
        rg: 'h3',
        sm: 'h4',
        xs: 'h5',
        xxs: 'h6',
      };
      
      function Heading({ size, children }) {
        return React.createElement(
          elements[size] || elements.rg, 
          props, 
          children
        );
      }
      
      Heading.defaultProps = {
        size: 'rg',
      };
      
      export default Heading;
      

      你可以像这样使用它

      <Heading size="sm">Some Heading</Heading>
      

      【讨论】:

        【解决方案5】:

        如果您使用的是 TypeScript,您会看到如下错误:

        Type '{ children: string; }' has no properties in common with type 'IntrinsicAttributes'.ts(2559)

        TypeScript 不知道 CustomTag 是一个有效的 HTML 标记名称,因此会抛出一个无用的错误。

        要修复,请将CustomTag 转换为keyof JSX.IntrinsicElements

        const CustomTag = `h${this.props.level}` as keyof JSX.IntrinsicElements;
        
        <CustomTag>Hello</CustomTag>
        

        【讨论】:

        • 我正在使用 TypeScript,但投射它会出现此错误:Types of property 'crossOrigin' are incompatible. Type 'string | undefined' is not assignable to type '"" | "anonymous" | "use-credentials" | undefined'. Type 'string' is not assignable to type '"" | "anonymous" | "use-credentials" | undefined'.
        • 只是想说声谢谢。如果这里不在这里,我可能会花费数小时尝试输入此内容。
        • 如何使用 Flow 做到这一点?
        • 我认为const Tag: keyof JSX.IntrinsicElements = `h${level}`; 会稍微好一些,因为如果您现在使用无效标签,例如headline${level} TypeScript 会抱怨。 (假设level 被正确键入为文字类型)
        【解决方案6】:

        你可以试试这个。我是这样实现的。

        import { memo, ReactNode } from "react";
        import cx from "classnames";
        
        import classes from "./Title.module.scss";
        
        export interface TitleProps {
          children?: ReactNode;
          className?: string;
          text?: string;
          variant: Sizes;
        }
        
        type Sizes = "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
        const Title = ({
          className,
          variant = "h1",
          text,
          children,
        }: TitleProps): JSX.Element => {
          const Tag = `${variant}` as keyof JSX.IntrinsicElements;
          return (
            <Tag
              className={cx(`${classes.title} ${classes[variant]}`, {
                [`${className}`]: className,
              })}
            >
              {text || children}
            </Tag>
          );
        };
        
        export default memo(Title);
        

        【讨论】:

          【解决方案7】:

          这就是我为我的项目设置它的方式。

          TypographyType.ts

          import { HTMLAttributes } from 'react';
          
          export type TagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span';
          
          export type HeadingType = HTMLAttributes<HTMLHeadingElement>;
          export type ParagraphType = HTMLAttributes<HTMLParagraphElement>;
          export type SpanType = HTMLAttributes<HTMLSpanElement>;
          
          export type TypographyType = (HeadingType | ParagraphType | SpanType) & {
            variant?:
              | 'h1'
              | 'h2'
              | 'h3'
              | 'h4'
              | 'h5'
              | 'h6'
              | 'body1'
              | 'body2'
              | 'subtitle1'
              | 'subtitle2'
              | 'caption'
              | 'overline'
              | 'button';
          };
          

          Typography.tsx

              import { FC } from 'react';
              import cn from 'classnames';
              import { typography } from '@/theme';
              
              import { TagType, TypographyType } from './TypographyType';
              
              const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
              const paragraphs = ['body1', 'body2', 'subtitle1', 'subtitle2'];
              const spans = ['button', 'caption', 'overline'];
              
              const Typography: FC<TypographyType> = ({
                children,
                variant = 'body1',
                className,
                ...props
              }) => {
                const { variants } = typography;
              
                const Tag = cn({
                  [`${variant}`]: headings.includes(variant),
                  [`p`]: paragraphs.includes(variant),
                  [`span`]: spans.includes(variant)
                }) as TagType;
              
                return (
                  <Tag
                    {...props}
                    className={cn(
                      {
                        [`${variants[variant]}`]: variant,
                      },
                      className
                    )}
                  >
                    {children}
                  </Tag>
                );
              };
              
              export default Typography;
          

          【讨论】:

            【解决方案8】:

            概括robstarbuck's answer 你可以像这样创建一个完全动态的标签组件:

            const Tag = ({ tagName, children, ...props }) => (
              React.createElement(tagName, props , children)
            )
            

            你可以像这样使用:

            const App = ({ myTagName = 'h1' }) => {
              return (
                <Tag tagName={myTagName} className="foo">
                 Hello Tag!
                </Tag>
              )
            }
            

            【讨论】:

              猜你喜欢
              • 2017-05-28
              • 2016-04-19
              • 2015-05-04
              • 2015-07-04
              相关资源
              最近更新 更多