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

我试着这样做:

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

我期望的输出是这样的:

<h1>Hello</h1>

但这行不通。

有什么办法可以做到吗?


当前回答

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

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

其他回答

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

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

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

看例子

概括robstarbuck的答案,你可以创建一个完全动态的标签组件,像这样:

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

你可以这样用:

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

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

类型'{children:字符串;}'与'IntrinsicAttributes'类型没有共同的属性。ts(2559)

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

为了解决这个问题,将CustomTag转换为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);

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

这样更安全一点。即使你的打字检查失败了 返回一个合适的组件。 它更具有声明性。任何人只要看看这个组件就能知道它能返回什么。 它更灵活,例如取代'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>