React JSX 中的动态标签名称
Dynamic tag name in React JSX
我正在尝试为 HTML 标题标签(h1
、h2
、h3
等)编写 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 都被传递了。
所有其他答案都很好,但我会添加一些额外的,因为这样做:
- 安全一点。即使您的 type-checking 仍然让您失望
return 一个合适的组件。
- 它更具声明性。任何人通过查看此组件都可以了解它的功能 return。
- 它比 '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>
)
}
我正在尝试为 HTML 标题标签(h1
、h2
、h3
等)编写 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 invokeReact.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 都被传递了。
所有其他答案都很好,但我会添加一些额外的,因为这样做:
- 安全一点。即使您的 type-checking 仍然让您失望 return 一个合适的组件。
- 它更具声明性。任何人通过查看此组件都可以了解它的功能 return。
- 它比 '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>
)
}