如何将HTML字符串呈现为真正的HTML?

355
这是我尝试过的并且出现问题的内容。
这个可以正常工作:
<div dangerouslySetInnerHTML={{ __html: "<h1>Hi there!</h1>" }} />

这个不行:

<div dangerouslySetInnerHTML={{ __html: this.props.match.description }} />

描述属性只是普通的HTML内容字符串,但由于某种原因,它被呈现为字符串而不是HTML。

enter image description here

任何建议?
14个回答

268

this.props.match.description 是字符串还是对象?如果是字符串,它应该能够很好地转换为HTML。例如:

class App extends React.Component {

constructor() {
    super();
    this.state = {
      description: '<h1 style="color:red;">something</h1>'
    }
  }
  
  render() {
    return (
      <div dangerouslySetInnerHTML={{ __html: this.state.description }} />
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

结果:http://codepen.io/ilanus/pen/QKgoLA?editors=1011

但是,如果描述是没有引号''<h1 style="color:red;">something</h1>,你将会得到:

Object {
$$typeof: [object Symbol] {},
  _owner: null,
  key: null,
  props: Object {
    children: "something",
    style: "color:red;"
  },
  ref: null,
  type: "h1"
}
如果它是一个字符串并且你看不到任何HTML标记,我唯一看到的问题就是错误的标记。 更新 如果你正在处理HTML实体,你需要在发送到"dangerouslySetInnerHTML"之前将它们解码,这就是为什么它被称为 "危险" :) 工作示例:
class App extends React.Component {

  constructor() {
    super();
    this.state = {
      description: '&lt;p&gt;&lt;strong&gt;Our Opportunity:&lt;/strong&gt;&lt;/p&gt;'
    }
  }

   htmlDecode(input){
    var e = document.createElement('div');
    e.innerHTML = input;
    return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
  }

  render() {
    return (
      <div dangerouslySetInnerHTML={{ __html: this.htmlDecode(this.state.description) }} />
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

this.props.match.description 是一个字符串,而不是一个对象。你说的错误标记是什么意思?你是指未关闭的标签吗?React 应该只是渲染它,对吧? - Sergio Tapia
你能把console.log(this.props.match.description);粘贴在这里吗? - Ilanus
一个例子:<p><strong>我们的机会:</strong></p> - Sergio Tapia
在这种情况下,您需要使用.innerHTML或解码HTMLEntities。 - Ilanus
function htmlDecode(input){ var e = document.createElement('div'); e.innerHTML = input;var returnString = ''; for (index = 0; index < e.childNodes.length; index++) { // case of just a string if(e.childNodes[index].nodeValue){ returnString += e.childNodes[index].nodeValue; } // case of HTML if(e.childNodes[index].outerHTML){ returnString += e.childNodes[index].outerHTML; } } return returnString;} - Chris Adams
关于名称,它与HTML实体无关,而是与XSS攻击有关。请参见https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml。如果您设置的HTML是用户提供的,则存在危险。 - Chris Chudzicki

149
我使用'react-html-parser'。
yarn add react-html-parser
import ReactHtmlParser from 'react-html-parser'; 

<div> { ReactHtmlParser (html_string) } </div>

来源 npmjs.com

将@okram的评论提高可见性:

根据其Github描述:将HTML字符串直接转换为React组件,避免使用dangerouslySetInnerHTML。这是一个将HTML字符串转换成React组件的实用工具。它避免了使用dangerouslySetInnerHTML,并将标准的HTML元素、属性和内联样式转换为它们的React等效物。


24
这个库在后台使用了 "dangerouslySetInnerHTML" 吗? - Omar
11
从 GitHub 描述: 将 HTML 字符串直接转换为 React 组件,避免使用 dangerouslySetInnerHTML。从 npmjs.com: 将 HTML 字符串转换为 React 组件的实用工具。避免使用 dangerouslySetInnerHTML,并将标准的 HTML 元素、属性和内联样式转换为它们的 React 等效项。 - okram
9
对于React 17.0+,我使用一个类似的库叫做“html-react-parser”,目前它得到了支持。https://www.npmjs.com/package/html-react-parser - Marty McGee
8
HTML-react-parser的FAQ部分指出:"该库不是XSS(跨站脚本攻击)安全的。" https://www.npmjs.com/package/html-react-parser - Aniket Suryavanshi
你可能需要使用以下命令进行安装:npm install react-html-parser - james.c.funk

78

检查一下你要附加到节点的文本是否像这样没有转义:

var prop = {
    match: {
        description: '&lt;h1&gt;Hi there!&lt;/h1&gt;'
    }
};

不要这样写:

var prop = {
    match: {
        description: '<h1>Hi there!</h1>'
    }
};

如果文本被转义了,你需要在服务器端将其转换。

因为被转义,所以该节点是文本

该节点是文本,因为它被转义。

因为没有被转义,所以该节点是DOM节点

该节点是DOM节点,因为它没有被转义。


5
问题在于描述字符串被转义为 HTML 格式。我对其进行了反转义,现在它可以正常工作了。 - Sergio Tapia
6
请避免使用dangerouslySetInnerHTML,而是在React v16中使用Fragment。请查看@brad-adams的下一个答案 - Kunal Parekh
5
感谢提及 @KunalParekh,但它们是不同的东西。如果 HTML 位于您的应用程序内部(也就是说它实际上是 JSX),那么我的答案才是有效的。要将来自外部源的 HTML 解析为 JSX,您需要寻找另一种解决方案。 - Brad Adams
1
如何转换?使用哪个API或包?答案能让你完成一半。 - Tsar Bomba
@TsarBomba 有很多种方法可以做到这一点:https://npm.im/html-entities - undefined

64

如果您需要处理字符串中的HTML,我建议使用一个名为html-react-parser的包。

安装

NPM:

npm install html-react-parser

yarn:

yarn add html-react-parser

使用方法

import parse from 'html-react-parser'
const yourHtmlString = '<h1>Hello</h1>'

代码:

<div>
    {parse(yourHtmlString)}
</div>

2
谢谢,这也适用于Next.JS。 - Binara Medawatta
1
可以确认它工作得很好! - Michael
1
提醒,此软件包存在安全风险,因为它未对 HTML 进行净化。只能与类似 sanitize-html 的软件包一起使用:https://github.com/apostrophecms/sanitize-html - greenie-beans

25

如果您可以控制包含HTML的字符串来自何处(即在应用程序中的某个地方),则可以使用新的<Fragment> API进行如下操作:

如果您可以控制包含HTML的字符串来自何处(即在应用程序中的某个地方),则可以使用新的<Fragment> API进行如下操作:

import React, {Fragment} from 'react'

const stringsSomeWithHtml = {
  testOne: (
    <Fragment>
      Some text <strong>wrapped with strong</strong>
    </Fragment>
  ),
  testTwo: `This is just a plain string, but it'll print fine too`,
}

...

render() {
  return <div>{stringsSomeWithHtml[prop.key]}</div>
}

29
您的示例中没有包含HTML的字符串。它可能是JSX或纯字符串。 - mrkvon
3
是的,技术上你是正确的@mrkvon,但正如我所提到的,如果所述的“html”/jsx是你有控制权的内容,那么这个“解决方案”才是有效的。例如,不适用于通过API提供的某些“原始”HTML的呈现。在Fragment API之前,对我来说总是很麻烦,需要额外的span包裹,有时会影响flex布局。当我在寻找可能的解决方案时偶然发现了这个问题,我想分享一下我是如何解决的。 - Brad Adams
2
谢谢!这是我情况下唯一有效的解决方案。此外,回应mrkvon在这个答案中的评论:这个答案确实包含HTML,即“Some text <strong>wrapped with strong</strong>”包含HTML标签“strong”。 - Binita Bharati
3
@BinitaBharati 但是那不是一个字符串。如果你从API中获取到一个字符串,比如"<p>This is a String</p>"(或者简单地将一个字符串存储在变量中),当你将这个字符串放入<Fragment>中时,输出仍然会包含<p>标签。 - Muchdecal
1
@BradAdams。不错的技巧。我可以看到它在某些情况下非常方便。 - Muchdecal
显示剩余5条评论

25
我使用innerHTML和一个指向span的引用。
import React, { useRef, useEffect, useState } from 'react';

export default function Sample() {
  const spanRef = useRef<HTMLSpanElement>(null);
  const [someHTML,] = useState("some <b>bold</b>");

  useEffect(() => {
    if (spanRef.current) {
      spanRef.current.innerHTML = someHTML;
    }
  }, [spanRef.current, someHTML]);

  return <div>
    my custom text follows<br />
    <span ref={spanRef} />
  </div>
}

关于XSS攻击的注意事项:为了防止恶意注入的脚本被执行,HTML代码需要进行清洁处理。请参考以下示例:
/**
 * example how to retrieve a reference to an html object
 */

import React, { useRef, useEffect } from 'react';
import * as DOMPurify from 'dompurify'; // add with yarn add DOMPurify and @types/DOMPurify

/**
 * this component can be used into another for example <Sample/>
 */
export default function Sample() {
    /**
     * 1) spanRef is now a React.RefObject<HTMLSpanElement>
     * initially created with null value
     */
    const spanRef = useRef<HTMLSpanElement>(null);

    /**
     * 2) later, when spanRef changes because html span element with ref attribute,
     * follow useEffect hook will triggered because of dependent [spanRef].
     * in an if ( spanRef.current ) that states if spanRef assigned to valid html obj
     * we do what we need : in this case through current.innerHTML
     */
    useEffect(() => {
        if (spanRef.current) {
            // follow malicious code injects
            // spanRef.current.innerHTML = "sample xss<img src onerror=\"alert('code exec')\"/>";

            // follow malicious code will sanitized to <img src>
            spanRef.current.innerHTML = DOMPurify.sanitize("sample xss prevented<img src onerror=\"alert('code exec')\"/>")
        }
    }, [spanRef]);

    return <div>
        my custom text follows<br />
        {/* ref={spanRef] will update the React.RefObject `spanRef` when html obj ready */}
        <span ref={spanRef} />
    </div>
}

我喜欢这个,不需要额外的库或者在没有这种奢侈条件下依赖于服务器端。受你的启发,但是我用了一个类组件来实现: this.message.current.innerHTML = this.state.selectedMessage.body; }``` body对我来说是转义后的HTML。 - webhound
一点解释就可以让答案更加出色。 - letsbondiway
请查看我的回答中的更新部分,@letsbondiway。 - Lorenzo Delana
@LorenzoDelana 感谢您提供详细的更新答案。现在它真的很有帮助。不过,我有一个问题——您认为这种解决方案是否存在任何安全风险?我的意思是像XSS、HTML注入之类的攻击。我的理解是,由于我们没有使用dangerouslySetInnerHTML,所以它是安全的。 - letsbondiway
@letsbondiway,直接设置HTML元素属性,如innerHTML,可能会存在安全问题,如果不应用安全标准的话。从我的角度来看,当然我可能会错过一些东西,如果您知道您在做什么以及这些如何被攻击者负面使用,那么就没有特定的问题。例如,您可以定期使用框架提供的标准输入框,这当然是好的,因为已经考虑了最佳实践,但是如果您将该文本作为原始SQL查询的一部分,则攻击者可能会注入一个重言以提取所有数据。 - Lorenzo Delana
@letsbondiway 我在代码中添加了DOMPurify HTML清洁器来防止XSS攻击。 - undefined

17

你可以使用React的dangerouslySetInnerHTML方法

<div dangerouslySetInnerHTML={{ __html: htmlString }} />

或者你可以使用这个简单的方法进一步实现:在React应用中原样呈现HTML


5

在我的情况下,我使用了react-render-html

首先通过npm i --save react-render-html安装该包。

然后,

import renderHTML from 'react-render-html';

renderHTML("<a class='github' href='https://github.com'><b>GitHub</b></a>")

同时,我不建议再使用react-render-html。由于snyk的原因,它的包健康得分非常低:https://snyk.io/advisor/npm-package/react-render-html Html-react-parser要好得多:https://snyk.io/advisor/npm-package/html-react-parser - Paul M.

3

使用React内置的dangerouslySetInnerHTML,并配合好的HTML清理器(如sanitize-html)。

<div dangerouslySetInnerHTML={{
  __html: sanitizeHtml(dirtyHTMLString)
}}></div>

我尝试过的所有HTML解析器都会剥离一些我不想要的HTML,而且花时间查找文档以弄清如何停止这种情况是不值得的。

通过像这样使用dangerouslySetInnerHTML,您可以对HTML进行消毒,使其不那么危险。


它能正常工作,但有一个问题是它没有正确显示空格。 - Binni kumari

2
我无法让npm buildreact-html-parser配合使用。然而,在我的情况下,我能够成功地利用https://reactjs.org/docs/fragments.html。我需要展示一些HTML Unicode字符,但它们不应直接嵌入JSX中。在JSX内部,它必须从组件的状态中获取。组件代码片段如下:
constructor() 
{
this.state = {
      rankMap : {"5" : <Fragment>&#9733; &#9733; &#9733; &#9733; &#9733;</Fragment> , 
                 "4" : <Fragment>&#9733; &#9733; &#9733; &#9733; &#9734;</Fragment>, 
                 "3" : <Fragment>&#9733; &#9733; &#9733; &#9734; &#9734;</Fragment> , 
                 "2" : <Fragment>&#9733; &#9733; &#9734; &#9734; &#9734;</Fragment>, 
                 "1" : <Fragment>&#9733; &#9734; &#9734; &#9734; &#9734;</Fragment>}
                };
}

render() 
{
       return (<div class="card-footer">
                    <small class="text-muted">{ this.state.rankMap["5"] }</small>
               </div>);
}

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