如何将HTML解析为React组件?

49
这是我的情景:
1.应用程序请求CMS(内容管理系统)的页面内容。
2.CMS返回"<div>Hi,<SpecialButton color="red">My Button</SpecialButton></div>"
3.应用程序使用提供的属性中的数据呈现相应的组件,消耗内容。
我无法想出如何以React方式执行第3步,请给予建议。
谢谢@Glenn Reyes,这里有一个展示问题的Sandbox
import React from 'react';
import { render } from 'react-dom';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const App = () => (
  <div dangerouslySetInnerHTML={{__html: htmlFromCMS}}>
  </div>
);

// expect to be same as
// const App = () => (
//   <div>Hi, 
//     <SpecialButton color="red">My Button</SpecialButton>
//   </div>
// );

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

这里有一个由Vuejs制作的实时演示。字符串"<div v-demo-widget></div>"可以被视为Vuejs指令并呈现。 源代码


我没有一个明确的答案给你,Andy,但根据你如何获取CMS数据,可能能够指引你一个方向。你尝试过使用高阶组件来渲染请求返回的组件吗?我认为一个渲染你所请求的组件的组件可能是正确的方法。 - Brett East
@BrettEast,一个高阶组件可以处理请求,但我的问题是从CMS获取像<h4>Hello</h4><reactcomponenct attr1="foo"></mycomponenct>这样的字符串后,如何让React知道<reactcomponenct attr1="foo"></mycomponenct>是组件,并执行组件的代码。 - Sing
是的,这个有点棘手。你的输入数据是否遵循 React 的命名约定,即使用大写字母表示 React 组件,小写字母表示 HTML 元素?也许可以用正则表达式来解决这个问题。 - Brett East
我认为遵循React的命名方式是可以的,但好像我需要编写一个编译器来实现类似Angular指令的功能... - Sing
7个回答

53

你可能希望深入了解 dangerouslySetInnerHTML。以下是在 React 组件中从字符串渲染 HTML 的示例:

import React from 'react';
import { render } from 'react-dom';

const htmlString = '<h1>Hello World! </h1>';

const App = () => (
  <div dangerouslySetInnerHTML={{ __html: htmlString }} />
);

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

完整的示例请参见:https://codesandbox.io/s/xv40xXQzE

在React文档中阅读有关dangerouslySetInnerHTML的更多信息,请点击此处:https://facebook.github.io/react/docs/dom-elements.html#dangerouslysetinnerhtml


2
如果htmlString包含一个像"<h4>Hello</h4><MyReactComponent attr1="foo"></MyReactComponent>"这样的react组件字符串,那么MyReactComponent将是一个HTML字符串而不是真正的组件。 - Sing
抱歉,各位,请给我一点时间,我会提供一个完整的示例以减少误解。 - Sing
1
有趣,我明白你在寻找什么。我在这里创建了一个代码沙盒:https://codesandbox.io/s/WnKvoY6BE - glennreyes
谢谢,我已经编辑了我的问题,将您的沙盒包含在其中并进行了一些调整。 - Sing
今天刚注意到。请随意将此答案标记为已解决,谢谢! - glennreyes
我认为问题仍未解决,HTML字符串仍无法被视为React组件。 - Sing

22
你可以使用 react-html-parser,如果你不想使用dangerouslySetInnerHTML属性。
import React from 'react';
import { render } from 'react-dom';
import ReactHtmlParser from 'react-html-parser';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const App = () => (
  <div>
     {ReactHtmlParser(htmlFromCMS)}
  </div>
);


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

愉快的编码!!!


4
这个库是否在其后台使用了 "dangerouslySetInnerHTML"? - Omar
6
react-html-parser 在内部不使用 dangerouslySetInnerHTML,而是使用 htmlparser2 - kca
1
问题在于它没有渲染按钮本身。我使用了一个特殊的html元素specialbutton。该组件未被渲染。 <specialbutton color="red">我的按钮</specialbutton> - Armalong
5
对于自定义组件,您需要使用 transform 选项:https://github.com/peternewnham/react-html-parser/issues/64#issuecomment-501006825 - Daniel Loureiro
那么区别在哪里呢?输入的HTML是否仍然可以包含JavaScript代码并被执行,或者react-html-parser会过滤掉可能危险的内容? - Joel M

21

正如 EsterlingAccimeYoutuber 在 回答中指出的那样,如果您不想使用 dangerouslySetInnerHTML 属性,可以使用 parser

到目前为止,react-html-parser 已经有 3 年没有更新了,因此我去寻找了一个不同的模块。

html-react-parser 可以完成相同的工作,而且得到了频繁的维护和更新。

为了防止 XSS 攻击,最好先对您的 html 字符串进行清理。可以使用 dompurify 完成。

我将 EsterlingAccimeYoutuber 的代码示例更新为以下内容:

import React from 'react';
import { render } from 'react-dom';
import parse from 'html-react-parser';
import DOMPurify from 'dompurify';

const SpecialButton = ({ children, color }) => (
  <button style={{color}}>{children}</button>
);

const htmlFromCMS = `
<div>Hi, 
  <SpecialButton color="red">My Button</SpecialButton>
</div>`;

const htmlFrom = (htmlString) => {
        const cleanHtmlString = DOMPurify.sanitize(htmlString,
          { USE_PROFILES: { html: true } });
        const html = parse(cleanHtmlString);
        return html;
}

const App = () => (
  <div>
     {htmlFromCMS && htmlFrom(htmlFromCMS)}
  </div>
);


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

受到上述原始帖子的启发,因此特别感谢原作者!


5
感谢指出维护良好的库html-react-parser,而非react-html-parser - Daniel Loureiro

2

如果要对GProst Answer进行任何未来的改进,您可以使用ReactDOMserver,以下是实现方式。

import React from "react";
import { render } from "react-dom";
import { renderToString } from "react-dom/server";

const SpecialButton = ({ children, color }) => (
  <button style={{ color }}>{children}</button>
);

const renderButton = renderToString(<SpecialButton>MyButton</SpecialButton>);

const htmlFromCMS = `
<div>Hi, 
  ${renderButton}
</div>`;

const App = () => <div dangerouslySetInnerHTML={{ __html: htmlFromCMS }} />;

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

0

通过使用dangerouslySetInnerHTML属性,实现解析器的最简单和最容易的方法。

const htmlString = '<h1>Hello World! </h1>';
const App = () => (
  <div dangerouslySetInnerHTML={{ __html: htmlString }} />
);

0

这是我将html-react-parser和react onClick事件结合使用的方法。

import React from "react";
import { render } from "react-dom";
import parse from "html-react-parser";

const html = `
  <div style="font-size:32px;">html-react-parser with js events</div>
  <div>This is a long long long text.<div id="supportEmail"></div>t is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</div>
`;

const handlefunction = () => {
  alert("Clicked");
};

const replace = (domNode) => {
  if (domNode.attribs && domNode.attribs.id === "supportEmail") {
    return (
      <code>
        <div
          style={{
            backgroundColor: "gray",
            padding: "4px 8px",
            width: "100px",
            textAlign: "center"
          }}
          onClick={handlefunction}
        >
          Click
        </div>
      </code>
    );
  }
};

function App() {
  return parse(html, { replace });
}

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

查看示例请访问Codesandbox


0

根据ReactDOMServer的说明,RenderToString接受一个组件作为参数而不是像"<MyReactComponent />"这样的字符串,因此无法正确渲染。 - Sing
首先,您需要通过组件名称找到相应的类。然后进行渲染。我假设您可以在服务器端(在CMS中)轻松完成此操作。否则,您将需要解析整个字符串,分离纯HTML和React组件,然后在另一个组件内一起渲染它们。无论如何,这不是一个简单的任务,我建议您找到一些解决方法。 - GProst
我明白了,你的建议是将组件逻辑放在CMS中,然后CMS返回渲染后的组件字符串,对吗? - Sing
是的,最大的问题在于解析字符串。如果您可以在CMS中轻松获取组件名称,则更容易在CMS中呈现组件,然后将其返回给客户端。 - GProst
Vuejs 可以更简单地实现这一点,因为它使用 html。ReactJS 使用 JSX,这是不同的东西。 - GProst
显示剩余2条评论

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