如何在React中呈现HTML注释?

53

目前,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注释的组件?换句话说,在上面示例中的“fragment”组件的渲染函数应该是什么样子的?


1
发布那条评论的人并不理解React的工作原理。请注意,那些了解它的人中没有一个建议它会起作用。首先,它没有解决核心问题;结果是四个节点(一个注释节点,两个元素节点,然后是一个注释节点),而不是单个节点。 - T.J. Crowder
我的理解是,Fragment的渲染函数只会返回带有两个子组件“c”和“d”的Fragment组件。因此在第二个注释中使用了结束标签“/fragment”。此外,似乎这种技术已经被用于实现mwiencek/react分支中的片段组件,在提交dcc972c414中,但我可能错了。 - Greg
嘿@Greg,希望我的解决方案有所帮助。很抱歉我不得不多次编辑/重构它。如果在我进行所有这些更改时您收到了太多通知,我深表歉意。 - kronus
仅供未来读者参考--自从2017年React.Fragment API被添加以来,这个问题的动机已经消失了。使用片段而不是HTML注释;-)。 - PatKilg
我不幸得出结论,React JSX 中无法输出简单的 HTML 注释。以下最佳解决方案将 HTML 注释注入 DIV 元素中。 - Dermot Doherty
我说得太早了。这个解决方案使用一些巫术 outerHTML 魔法来用注释节点替换注入的元素,从而实现结果。好技巧! useEffect(() => { ref.current.outerHTML = \<!--${text}-->`; }, [text]); return (<script ref={ref} type="text/placeholder" />);`https://dirask.com/posts/React-create-HTML-comment-comment-D7R861 - Dermot Doherty
10个回答

29

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

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>

谢谢,但据我理解,这段代码并没有回答我的问题。我的目的不是在React中呈现注释,而是从渲染函数返回一个单一元素,该元素呈现两个注释,一个在其子元素之上,另一个在其子元素之下。换句话说,我应该能够像这样使用它:<Fragment><C /><D /></Fragment>,并且它应该呈现带有两个注释的子元素,一个在上面,一个在下面,就像我问题中的示例一样。 - Greg
酷。创建自定义评论组件。 - zhuhang.jasper

14
您可以使用以下组件进行操作,它是简单而功能强大的,但缺点是您必须将您的评论包装在一个HTML节点中,即一个“div”,因为它使用了dangerouslySetInnerHTML属性:
    const ReactComment = ({ text }) => {
  return <div dangerouslySetInnerHTML={{ __html: `<!-- ${text} -->` }}/>
}

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

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

9
如果您需要与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]-->
  ` }}
  />
}

我认为这对于头元素内的条件标签不起作用,因为div不能在head内使用。 - zomars

5

在React中使用HTML注释

为了在React中渲染注释(我猜这就是大多数人来到这个问题时所寻找的),我使用了一个React组件,该组件在gist中。它基于Alex Zinkevych的答案,但进行了以下改进:

  • 现在更新props会触发组件更新,因此注释可以更加动态
  • 该组件会在使用后进行清理
  • 在注释节点被替换之前,div会被隐藏
  • (代码风格)React Ref代替ReactDOM.findDOMNode(this),这是React文档推荐与DOM元素交互的方式。

我上面链接了gist,但我也复制了此时此刻的内容,但你可能想查看gist是否有任何修订,因为我会修复我发现的任何错误并将其作为Gist的修订版发布。

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的帖子评论中指出的那样,这并没有真正回答问题。对于一个在children之前和之后呈现评论的单个组件,我们可以使用上面定义的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 Playground上的源代码,以及一个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,您将看到以下内容:

enter image description here


1
我认为这对于SSR不起作用,因为我们使用了componentDidMountwindow - zomars
取决于您想要实现什么。如果您希望服务器端呈现的页面中有内联注释,则不行。 - Jonathon Richardson
然而,如果您正在使用ReactDOM.hydrate()加载页面,则在生命周期方法触发后,您将能够在检查器中看到注释。您可以通过使用隐藏元素(例如<div hidden />)或可能使用自定义元素(<ns-comment hidden />)来在初始SSR中获取注释,并且您可以在SSR期间发出它,并使用生命周期方法在水合/运行时将自定义元素替换为真正的HTML注释,如果您想要的话。这有点取决于您为什么需要注释。 - Jonathon Richardson
FYI:https://github.com/facebook/react/issues/21601 - Jonathon Richardson

3
我在这里看到一些答案建议使用类似于{'<!-- comment -->'}的语法,它会在浏览器中将<!-- comment -->作为<p>显示出来。如果您使用相同的方法与一个ref配合使用,并设置ref.current.outerHTML = '<!-- comment -->',那么这个方法可能有效,但是这样做非常繁琐,并需要使用useEffect、useRef和大量额外的代码。而且你仍然需要创建一个可丢弃的div,用评论替换它,除非你特意想要欺骗用户认为你添加了一个注释(如果他们知道如何检查页面并查看注释,那么他们很可能也知道如何阅读您发送的React JS)。
当我想添加注释时,我使用的一个非常简单和紧凑的解决方案是:
<div style={{display:'none'}}>
    comment
</div>

2

我觉得有必要在这里发布我的答案,因为这是我第一次搜索到的地方。

我知道这是一种hack方法,但对于我的用例来说,这允许我注入任意html代码到head标签中:

const DangerousRawHtml = ({ html = "" }) => (
  <script dangerouslySetInnerHTML={{ __html: `</script>${html}<script>` }} />
);

使用方法:


const EmailHead = ({ title = "" }) => {
  return (
    <head>
      <title>{title}</title>
      <DangerousRawHtml html={`<!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->`} />
    </head>
  )
}

输出结果会留下一些空的 script 标签,这并不是最优的方式,但它能正常工作。
<html>
  <head>
    <title>Title</title>
    <script></script>
    <!--[if !mso]><!-->
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <!--<![endif]-->
    <script></script>
  </head>
  <body></body>
</html>

如果你计划使用renderToStaticMarkup,你可以这样清除空的脚本:

ReactDOMServer.renderToStaticMarkup(<MyRootComponent />)
  // Remove `<DangerousRawHtml />` injected scripts
  .replace(/<script><\/script>/g, "")

0
假设您正在使用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>

3
当使用ReactDOMServer.renderToStaticMarkup时,这个解决方案似乎不起作用。 - FibreFoX
当React试图卸载组件时,由于找不到DOM中期望的子节点,它也会崩溃。 - omnibrain
对我来说,在卸载期间不会崩溃,但我对任何其他缺点都很感兴趣。 - Abhishiv Saxena
你在 ReactComment 组件中使用了 Prettier 吗? - Qwerty
@FibreFoX 你可以查看我的答案,它与 redenerToStaticMarkup 兼容。 - zomars

0

这个方法可行,而且会给你正确的注释标签,而不是一个 div...

useEffect(() => {
    const c = document.createComment(`Hi there!`);
    document.appendChild(c);
    return () => document.removeChild(c);
}, []); 

-1
创建一个名为Comment.js的功能组件。
使用jQuery和本地JavaScript document.createComment结合使用来选择divs。
使用props传递要在评论中使用的文本以及要选择的divs的名称:
import $ from 'jquery';

const Comment = (props) => {
  const commentMakerBegin = () => {
    setTimeout(() => {
      const beginComment = document.createComment(props.beginComment);
      const firstElement = $('.' + props.beforeDiv);
      firstElement.before(beginComment);
    }, 1000);
  };

  const commentMakerEnd = (event) => {
    setTimeout(() => {
      const endComment = document.createComment(props.endComment);
      const secondElement = $('.' + props.afterDiv);
      secondElement.after(endComment);
    }, 1000);
  };
  return (
    <>
      {commentMakerBegin()}
      {props.children}
      {commentMakerEnd()}
    </>
  );
};

export default Comment;

props.children 会渲染在你自定义组件标签之间的任何内容:

{props.children}

无论您键入像“Your components here”或“<C /><D />”这样的字符串,它都会呈现在打开和关闭标签之间键入的内容。
在您想要使用新创建的Comment组件的组件中,先导入它,然后通过props传递您想要用于打开和关闭注释的文本。
下面的图片是我在我的两个模态consumer-modal和policy-modal之前和之后呈现注释的方式。

You can see in the console that the comments have been added

在我的 App.js 文件中,我导入了 Comments 组件,并以以下方式使用它,从而导致了上述屏幕截图:
     <Comment
        beforeDiv='consumer-modal'
        afterDiv='policy-modal'
        beginComment='modal begins'
        endComment='modal ends'
      >
        <ConsumerModal
          title='testing'
          content={<ConsumerModalContent />}
          onClose={cnsmrModalHandler}
        ></ConsumerModal>
        <PolicyModal
          title='my policy'
          content={<PolicyModalContent />}
          onClose={policyModalHandler}
        />
      </Comment>

-13

编辑:对于那些发现这个答案有用的人,请查看this answer

发布的问题并不是在询问React中的注释样式!


使用花括号,这样您就可以使用JavaScript注释/* */
<a>
    <b></b>
    {/*<fragment data-reactid="">*/}
        <c></c>
        <d></d>
    {/*</fragment>*/}
    <e></e>
</a>

22
仅仅因为一个回答并没有给出解决问题的方案就能获得6个赞?!这只是关于如何在组件代码中添加注释,但(就像我一样)提问者想要在渲染后的HTML中输出注释! - Andy Lorenz
这不会将注释呈现为HTML注释 <!-- comment -->。它们甚至不会出现在缩小的源代码中,因为转译器会将它们删除。 - Jake Sylvestre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接