React JSX 中的动态标签名称

问题描述 投票:0回答:10

我正在尝试为 HTML 标题标签(

h1
h2
h3
等)编写一个 React 组件,其中标题级别是通过 prop 指定的。

我尝试这样做:

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

我期望的输出如下:

<h1>Hello</h1>

但这不起作用。

有什么办法可以做到这一点吗?

javascript node.js reactjs jsx
10个回答
588
投票

无法就地执行此操作,只需将其放入变量中(首字母大写):

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

<CustomTag>Hello</CustomTag>

188
投票

如果您使用 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;
// or to let TypeScript check if the tag is valid
// const CustomTag : keyof JSX.IntrinsicElements = `h${this.props.level}`;

<CustomTag>Hello</CustomTag>

54
投票

为了完整起见,如果想使用动态名称,也可以直接调用

React.createElement
,而不是使用 JSX:

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

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

搭配道具:

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

来自文档

创建并返回给定类型的新 React 元素。类型参数可以是标签名称字符串(例如

'div'
'span'
),也可以是 React 组件类型(类或函数)。

使用 JSX 编写的代码将转换为使用

React.createElement()
。如果您使用 JSX,您通常不会直接调用
React.createElement()
。请参阅 React Without JSX 了解更多信息。


26
投票

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

  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>

12
投票

在动态标题 (h1, h2...) 的实例中,组件可以像这样返回

React.createElement
(上面由 Felix 提到)。

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

为了可组合性,道具和子项都被传递。

参见示例


6
投票

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

排版类型.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';
};

版式.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;

3
投票

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

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

3
投票
//for Typescript
interface ComponentProps {
    containerTag: keyof JSX.IntrinsicElements;
}

export const Component = ({ containerTag: CustomTag }: ComponentProps) => {
    return <CustomTag>Hello</CustomTag>;
}

2
投票

概括robstarbuck的答案您可以创建一个完全动态的标签组件,如下所示:

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

你可以这样使用:

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

0
投票

React + TypeScript

这个实现为 OP 的问题定义了一个简洁的类型安全的可组合方法。它定义了一个 props 类型,该类型将对应于任何有效标题标签的属性以及任何有效的 React 组件属性(例如

className
children
)。此外,如果
level
的值不是预期的 1 到 6,您将收到 TypeScript 错误。它还解决了之前答案中的“联合”错误。

HeadingWithRef
示例更进一步,允许父级访问生成的 DOM 属性,以防您需要对其进行动态处理或读取原始元素的属性值。

//Define the heading properties type
interface HeadingProps
  extends React.ComponentProps<"h1" | "h2" | "h3" | "h4" | "h5" | "h6"> {
  level: 1 | 2 | 3 | 4 | 5 | 6;
}

//Define the component
const Heading: React.FC<HeadingProps> = ({ level, ...props }) => {
  const Tag: keyof JSX.IntrinsicElements = `h${level}`;

  return <Tag {...props} />;
};

//If you need ref forwarding
const HeadingWithRef = React.forwardRef<HTMLHeadingElement, HeadingProps>(
  ({ level, ...props }, ref) => {
    const Tag: keyof JSX.IntrinsicElements = `h${level}`;

    return <Tag ref={ref} {...props} />;
  }
);
HeadingWithRef.displayName = "HeadingWithRef";

使用示例

//This will render an <h2> tag
export default function DemoPage() {
  return (
    <>
      <header>
        <Heading level={2}>Heading</Heading>
      </header>
    </>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.