React JSX中的动态标签名称

我试图为HTML标题标签(h1h2h3等)编写一个React组件,其中标题级别是通过道具指定的。

我试着这样做:

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

我期望的输出是这样的:

<h1>Hello</h1>

但这行不通。

有什么办法可以做到吗?

132970 次浏览

没有办法做到这一点,只是把它放在一个变量(首字母大写):

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

文档:

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

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

在动态标题(h1, h2…)的实例中,组件可以返回React.createElement(上面由费利克斯提到),如下所示。

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

为了可组合性,同时传递了道具和子元素。

参见示例

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

    这样更安全一点。即使你的打字检查失败了 返回一个合适的组件。李< / >
  1. 它更具有声明性。任何人只要看看这个组件就能知道它能返回什么。
  2. 它更灵活,例如取代'h1', 'h2',…对于你的标题类型你可以用一些抽象的概念" sm " " lg "或者" primary " " secondary "

Heading组件:

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;

泛化robstarbuck的回答,你可以像这样创建一个完全动态的标签组件:

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

你可以这样用:

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