如何在 React 中渲染 HTML 注释?

IT技术 javascript html dom reactjs react-jsx
2021-04-01 13:07:46

目前 render 方法只能返回单个元素/组件。见:这里

在该票证下的讨论中,一些建议将从 React 组件返回的多个元素包装在 HTML 注释中,以便浏览器忽略包装组件,例如:

<A>
    <B></B>
    <Fragment>
        <C></C>
        <D></D>
    </Fragment>
    <E></E>
</A>

将呈现为:

<a>
    <b></b>
    <!--<fragment data-reactid="">-->
        <c></c>
        <d></d>
    <!--</fragment>-->
    <e></e>
</a>

但是如何真正创建一个只呈现 HTML 注释的组件呢?换句话说,上面示例中“片段”组件的渲染功能会是什么样子?

6个回答

这就是我在最近的一个项目中得到的结果:

import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';

class ReactComment extends Component {
    static propTypes = {
        text: PropTypes.string,
        trim: PropTypes.bool
    };

    static defaultProps = {
        trim: true
    };

    componentDidMount() {
        let el = ReactDOM.findDOMNode(this);
        ReactDOM.unmountComponentAtNode(el);
        el.outerHTML = this.createComment();
    }

    createComment() {
        let text = this.props.text;

        if (this.props.trim) {
            text = text.trim();
        }

        return `<!-- ${text} -->`;
    }

    render() {
        return <div />;
    }
}

export default ReactComment;

所以你可以像这样使用它:

<A>
    <B></B>
    <ReactComment text="<fragment>" />
        <C></C>
        <D></D>
     <ReactComment text="</fragment>" />
    <E></E>
</A>
凉爽的。创建用于评论的自定义组件。
2021-05-30 13:07:46
谢谢,但据我了解这段代码,它没有回答我的问题。我的目标不是在 React 中渲染评论,而是从渲染函数返回单个元素,该元素渲染两个评论,一个在其子元素之上,一个在其子元素之下。换句话说,我应该能够像这样使用它:<Fragment><C /><D /></Fragment>并且它应该为孩子们呈现两条评论,一条在上面,一条在下面,如我问题中的示例所示。
2021-06-05 13:07:46

如果您需要它与 SSR 一起使用,这是另一种新颖的方法。

这是MaxWidth我在基于 react 的电子邮件工具中使用的一个组件,名为Myza

import ReactDOMServer from 'react-dom/server'

export const MaxWidth = ({ maxWidth = 0, className, children }: IMaxWidthProps) => {
  const renderedChildren = ReactDOMServer.renderToStaticMarkup(
    <div className={className} style={{ maxWidth: `${maxWidth}px`, margin: '0 auto' }}>
      {children}
    </div>
  )

  return <div dangerouslySetInnerHTML={{
    __html: `
    <!--[if mso]><center><table><tr><td width="${maxWidth}"><![endif]-->
    ${renderedChildren}
    <!--[if mso]> </td></tr></table></center><![endif]-->
  ` }}
  />
}

您可以使用以下组件来完成它,它既简单又实用,但它的缺点是必须将您的评论包装在 HTML 节点中,即“div”,因为它使用了危险的SetInnerHTML 属性:

    const ReactComment = ({ text }) => {
  return <div dangerouslySetInnerHTML={{ __html: `<!-- ${text} -->` }}/>
}

然后,您可以像这样使用它:

<ReactComment text={'My beautiful HTML comment'}/>

React 中的 HTML 注释

为了在 React 中呈现评论(我猜这是大多数人在遇到这个问题时正在寻找的),我使用了一个我在gist 中拥有的 react 组件它基于Alex Zinkevych答案,但有以下改进:

  • 现在更新 props 会触发组件更新,所以评论可以更动态
  • 组件自行清理
  • div 在被换出评论节点之前是隐藏的
  • (代码样式)ReactDOM.findDOMNode(this)根据 React 的文档,使用 React Ref 代替,这是与 DOM 元素交互的推荐方式。

我链接到了上面的要点,但在撰写本文时我也复制了下面的内容,但您可能想看看要点是否有任何修订,因为我会修复我可能发现的任何错误并作为修订发布到要点。

import * as React from 'react';
import * as ReactDOM from 'react-dom';

interface IProps {
    text: string;
}

export class HTMLComment extends React.Component<IProps> {
    private node: Comment;
    private ref$rootDiv = React.createRef<HTMLDivElement>();

    constructor(props: IProps) {
        super(props);

        this.node = window.document.createComment(props.text);
    }

    componentDidMount() {
        if (this.ref$rootDiv && this.ref$rootDiv.current) {
            let divElement = this.ref$rootDiv.current;

            // Tell React not to update/control this node
            ReactDOM.unmountComponentAtNode(divElement);

            // Replace the div with our comment node
            this.ref$rootDiv.current.replaceWith(this.node);
        }
    }

    componentDidUpdate(prevProps: IProps) {
        if (prevProps.text !== this.props.text) {
            this.node.textContent = this.props.text;
        }
    }

    componentWillUnmount() {
        this.node.remove();
    }

    render() {
        return (
            <div
                ref={this.ref$rootDiv}
                style={{
                    display: 'none',
                }}
            />
        );
    }
}

回答实际问题

但是,正如 OP 在对 Alex 帖子的评论中指出的那样,这实际上并没有回答这个问题。对于在子组件前后呈现评论的单个组件,我们可以使用上面定义的 HTMLComment 组件并组合一个新组件:

interface IHTMLCommentWrapperProps {

}

const HTMLCommentWrapper: React.FunctionComponent<IHTMLCommentWrapperProps> = (props) => {
    return (
        <React.Fragment>
            <HTMLComment text={`<fragment data-reactid="">`} />
            {props.children}
            <HTMLComment text={`</fragment>`} />
        </React.Fragment>
    )
}

现在,我们可以将所有这些放在一个脚本中。 这是Typescript 操场上的源代码以及Gist(它很大并且重复了上面详述的组件,因此我不会将该代码直接复制到此答案中。

我们可以将编译后的 javascript 复制到下面的代码段中:

class HTMLComment extends React.Component {
    constructor(props) {
        super(props);
        this.ref$rootDiv = React.createRef();
        this.node = window.document.createComment(props.text);
    }
    componentDidMount() {
        if (this.ref$rootDiv && this.ref$rootDiv.current) {
            let divElement = this.ref$rootDiv.current;
            // Tell React not to update/control this node
            ReactDOM.unmountComponentAtNode(divElement);
            // Replace the div with our comment node
            this.ref$rootDiv.current.replaceWith(this.node);
        }
    }
    componentDidUpdate(prevProps) {
        if (prevProps.text !== this.props.text) {
            this.node.textContent = this.props.text;
        }
    }
    componentWillUnmount() {
        this.node.remove();
    }
    render() {
        return (React.createElement("div", { ref: this.ref$rootDiv, style: {
                display: 'none',
            } }));
    }
}
const HTMLCommentWrapper = (props) => {
    return (React.createElement(React.Fragment, null,
        React.createElement(HTMLComment, { text: `<fragment data-reactid="">` }),
        props.children,
        React.createElement(HTMLComment, { text: `</fragment>` })));
};
const A = (props) => { return React.createElement("a", null, props.children); };
const B = (props) => { return React.createElement("b", null, props.children); };
const C = (props) => { return React.createElement("c", null, props.children); };
const D = (props) => { return React.createElement("d", null, props.children); };
const E = (props) => { return React.createElement("e", null, props.children); };
const App = () => {
    return (React.createElement(A, null,
        React.createElement(B, null),
        React.createElement(HTMLCommentWrapper, null,
            React.createElement(C, null),
            React.createElement(D, null)),
        React.createElement(E, null)));
};
let el$root = document.getElementById('react-app');
if (el$root) {
    ReactDOM.render(React.createElement(App, null), el$root);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="react-app"/>

如果您运行此代码段并检查 HTML,您将看到以下内容:

在此处输入图片说明

假设您使用的是 React 16.8+,您可以使用一个小的功能组件,它允许您提供文本属性并呈现 html 注释。

import React, {useEffect, useRef} from 'react';

const ReactComment = ( props ) => {
    const el = useRef();
    useEffect( () => {
        el.current.outerHTML = `<!-- ${props.text} -->`;
    }, [] );
    return (
        <div ref={el}/>
    );
};

export default ReactComment;

然后你可以像这样使用它

<A>
    <B></B>
    <ReactComment text="<fragment>" />
        <C></C>
        <D></D>
     <ReactComment text="</fragment>" />
    <E></E>
</A>
当 react 尝试卸载组件时它也会崩溃,因为它在 DOM 中找不到它期望的子节点。
2021-05-25 13:07:46
此解决方案在使用时似乎不起作用 ReactDOMServer.renderToStaticMarkup
2021-06-07 13:07:46
在我卸载时不会崩溃,但对任何其他缺点感兴趣
2021-06-16 13:07:46