【问题标题】:How to render a HTML comment in React?如何在 React 中呈现 HTML 注释?
【发布时间】:2017-02-22 05:50:21
【问题描述】:

目前渲染方法只能返回单个元素/组件。见:here

在该票下的讨论中,一些人建议将从 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'组件的渲染函数应该是怎样的?

【问题讨论】:

  • 发表该评论的人不了解 React 的工作原理。请注意,没有人建议它会起作用。一方面,它没有解决核心问题;结果是四个节点(一个注释节点,两个元素节点,然后是一个注释节点),而不是一个节点。
  • 我的理解是 Fragment 的渲染函数只会返回带有两个子组件 'c' 和 'd' 的 Fragment 组件。因此,第二条评论中的结束标签“/片段”。此外,似乎该技术已用于在提交 dcc972c414 中的 mwiencek/react fork 中实现片段组件,但我可能错了。
  • 嘿@Greg,我希望我的解决方案有所帮助。抱歉,我不得不对其进行几次编辑/重构。如果您在我进行所有这些更改时收到太多通知,我深表歉意。

标签: javascript html dom reactjs react-jsx


【解决方案1】:

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

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 中渲染评论,而是从渲染函数返回单个元素,该元素渲染两个 cmets,一个在其子级之上,一个在其子级之下。换句话说,我应该能够像这样使用它:&lt;Fragment&gt;&lt;C /&gt;&lt;D /&gt;&lt;/Fragment&gt; 它应该用两个 cmets 渲染孩子,一个在上面,一个在下面,就像我的问题中的示例一样。
  • 酷。为评论创建自定义组件。
【解决方案2】:

编辑:对于那些认为此答案有用的人,请查看this answer

发布的问题是 React 中没有要求评论样式!


使用大括号,这样您就可以使用 javascript 注释 /* */

<a>
    <b></b>
    {/*<fragment data-reactid="">*/}
        <c></c>
        <d></d>
    {/*</fragment>*/}
    <e></e>
</a>

【讨论】:

  • 6 票赞成一个甚至没有为所提出的问题提供解决方案的答案?!这只是将 cmets 放入组件代码中的方法,但是(像我一样)OP 想要 输出 cmets 到他呈现的 html 中!
  • 这不会将 cmets 渲染为 html cmets &lt;!-- comment --&gt;。它们甚至不会出现在缩小的源代码中,因为转译器会将它们取出
【解决方案3】:

假设您使用的是 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>

【讨论】:

  • 这个解决方案在使用ReactDOMServer.renderToStaticMarkup时似乎不起作用
  • 当 react 试图卸载组件时也会崩溃,因为它在 DOM 中找不到它所期望的子节点。
  • 对我来说在卸载过程中不会崩溃,但对任何其他缺点感兴趣
【解决方案4】:

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

这是一个MaxWidth 组件,我正在使用我的基于反应的电子邮件工具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]-->
  ` }}
  />
}

【讨论】:

    【解决方案5】:

    React 中的 HTML 注释

    为了在 React 中渲染 cmets(我猜大多数人在遇到这个问题时都在寻找),我使用了 gist 中的一个 react 组件。它基于 Alex Zinkevych 的 answer,但有以下改进:

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

    我链接到上面的要点,但我也在下面复制了撰写本文时的内容,但您可能想查看要点是否有任何修订,因为我会修复我可能发现的任何错误和作为对 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前后渲染cmets的单个组件,我们可以使用上面定义的HTMLComment组件,组成一个新组件:

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

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

    我们可以将编译好的javascript复制到下面的sn-p中:

    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"/>

    如果您运行这个 sn-p 并检查 HTML,您将看到以下内容:

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

        【解决方案7】:

        创建一个文件名为 Comment.js 的功能组件

        导入 jquery 以使用它们的类选择器选择 div,并结合使用本机 javascript document.createComment

        使用 props 传递要在 cmets 中使用的文本,以及要选择的 div 的名称:

        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”还是“&lt;C /&gt;&lt;D /&gt;”之类的字符串,它都会呈现您在开始标签和结束标签之间输入的内容。

        在您想要使用新创建的 Comment 组件的组件中,将其导入,然后通过您想要用于打开和关闭 cmets 的 props 传递文本

        下图是我如何在我的两个模态(消费者模态和策略模态)之前和之后渲染 cmets。

        在我的 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>
        

        【讨论】:

          【解决方案8】:

          我在这里看到一些答案说使用类似于{'&lt;!-- comment --&gt;'} 的语法,它只会在您的浏览器上将&lt;!-- comment --&gt; 显示为&lt;p&gt;,如果您对 ref 执行相同的操作然后设置它可能会起作用ref.current.outerHTML = '&lt;!-- comment --&gt;',但这非常繁琐,需要 useEffect、useRef 和大量额外代码。而且您仍然需要创建一个用评论替换的丢弃 div,因此除非您不遗余力地试图欺骗用户认为您添加了评论(如果他们知道如何检查页面和查看 cmets那么他们很可能也知道如何阅读您发送的 React JS)

          我想添加评论时使用的一个非常简单紧凑的解决方案是这样的:

          <div style={{display:'none'}}>
              comment
          </div>
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-10-21
            • 1970-01-01
            • 2021-12-13
            • 2016-06-18
            • 1970-01-01
            • 2018-01-31
            相关资源
            最近更新 更多