React JSX 中的动态标签名称

Dynamic tag name in React JSX

我正在尝试为 HTML 标题标签(h1h2h3 等)编写 React 组件,其中指定了标题级别通过道具。

我试过这样做:

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

和预期输出如:

<h1>Hello</h1>

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

无法就地执行此操作,只需将其放入变量 ():

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

<CustomTag>Hello</CustomTag>

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

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

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

配道具:

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

来自docs

Create and return a new React element of the given type. The type argument can be either a tag name string (such as 'div' or 'span'), or a React component type (a class or a function).

Code written with JSX will be converted to use React.createElement(). You will not typically invoke React.createElement() directly if you are using JSX. See React Without JSX to learn more.

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

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

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

See Example

所有其他答案都很好,但我会添加一些额外的,因为这样做:

  1. 安全一点。即使您的 type-checking 仍然让您失望 return 一个合适的组件。
  2. 它更具声明性。任何人通过查看此组件都可以了解它的功能 return。
  3. 它比 'h1'、'h2' 更灵活,...对于您的标题类型,您可以有一些其他抽象概念 'sm'、'lg' 或 'primary'、'secondary'

标题组件:

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>

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

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

TypeScript 不知道 CustomTag 是一个有效的 HTML 标签名称并抛出一个无用的错误。

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

// var name must start with a capital letter
const CustomTag = `h${this.props.level}` as keyof JSX.IntrinsicElements;

<CustomTag>Hello</CustomTag>

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

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

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

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 TypographyProps = (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, TypographyProps } from './TypographyType';
    
    const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
    const paragraphs = ['body1', 'body2', 'subtitle1', 'subtitle2'];
    const spans = ['button', 'caption', 'overline'];
    
    const Typography: FC<TypographyProps> = ({
      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;

泛化 您可以像这样创建一个完全动态的标签组件:

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

您可以像这样使用:

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