如何将包含HTML的字符串解析为React组件

16

我正在使用这个库:html-to-react,因为我发现可以将HTML字符串“编译”成React组件。

我们正在做一个基于小部件的系统,它们可能会随着时间的推移而改变(即添加/删除/更新等),因此我们计划将小部件HTML从数据库发送到前端,使用React完成。

我已经成功地使用HTML创建了一个简单的小部件,现在我正在尝试使这个小部件响应点击事件,所以我想添加来自HTML的onclick=method()方法或来自React的onClick={method}方法。然而,由于我在浏览器控制台中得到了这些错误,所以我无法获得所需的输出。

Warning: Invalid event handler property `onclick`. React events use the camelCase naming convention, for example `onClick`.
    in label
    in span
    in div
Warning: Invalid event handler property `onclick`. Did you mean `onClick`?
    in label
    in span
    in div
    in DireccionComponent (at App.js:51)
    in div (at App.js:50)
    in GridItem (created by ReactGridLayout)
    in div (created by ReactGridLayout)
    in ReactGridLayout (at App.js:49)
    in App (at index.js:11)

我使用的代码如下:

App.js

import React, { Component } from 'react';
import './App.css';
import DireccionComponent from './DireccionComponent.js';

class App extends Component {
    render() {
        return (
            <DireccionComponent />
        );
    }
}

export default App;

DireccionComponent.js

import React, {Component} from 'react';

var ReactDOMServer = require('react-dom/server');
var HtmlToReactParser = require('html-to-react').Parser;

let htmlInput = ''
            +   '<div>'
            +       '<center><label className="title">Widget</label></center>'
            +       '<span className="ui-float-label">'
            +           '<label htmlFor="float-input" onClick={this.myFunction}>Hello</label>'
            +       '</span>'
            +   '</div>';

var htmlToReactParser = new HtmlToReactParser();
var reactElement = htmlToReactParser.parse(htmlInput);
var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement);

class DireccionComponent extends Component {
    constructor(props) {
        super (props);

        this.myFunction = this.myFunction.bind(this);
    }

    render() {
        return (
            reactElement
        )
    }

    myFunction() {
        console.log('Hola');
        alert("Hola");
    }
}

export default DireccionComponent;

package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "html-to-react": "^1.3.3",
    "primereact": "^1.5.1",
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-dom-server": "0.0.5",
    "react-grid-layout": "^0.16.6",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

使用上述库将HTML解析为React组件,是否可以获得alert或类似的东西?如果可以,我该如何做?或者我做错了什么?


编辑

它不必特定是我使用的库,如果您使用过另一种类似的库并做过类似的事情,我也接受这些建议(注意,我不是在要求软件或库,而是如何在包含我的HTML的字符串中调用onClick方法或解决方法)


编辑 2

尝试了一些方法后,我发现删除这行代码:

var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement);

删除其中一个消息。该库使用该元素测试当前HTML和解析后的HTML是否相同。对于我的情况,我不需要它,但仍然会在控制台中留下当前错误:

 Warning: Invalid event handler property `onclick`. Did you mean `onClick`?
    in label
    in span
    in div
    in DireccionComponent (at App.js:47)
    in App (at index.js:11)

编辑3

经过一些调查,我在这个答案中发现了dangerousSetInnerHTML

然后我尝试按照这个答案所述,在我的HTML中放置一个alert,如下所示:

'<label htmlFor="float-input" onclick="alert(\'Hello\')">Hello2</label>'

而且它触发了{{alert}},但是如果我像下面的代码那样声明{{myFunction}}(注意,我尝试将其声明为一个类外部的{{function}},在类内部以及作为{{var myFunction = function(){...}}}全部和分开):
import React, {Component} from 'react';

var ReactDOMServer = require('react-dom/server');
var HtmlToReactParser = require('html-to-react').Parser;

let htmlInput = ''
            //+ '<div>'
            +       '<center><label className="title">Widget</label></center>'
            +       '<span className="ui-float-label">'
            +           '<label htmlFor="float-input" onclick="myFunction()">Hello2</label>'
            +       '</span>'
            //+     '</div>';

var htmlToReactParser = new HtmlToReactParser();
var reactElement = htmlToReactParser.parse(htmlInput);

class DireccionComponent extends Component {
    constructor(props) {
        super (props);

        this.myFunction = this.myFunction.bind(this);
    }

    render() {
        return (
            <div dangerouslySetInnerHTML={{__html: htmlInput}}>

            </div>
        )
    }

    myFunction() {
        console.log('Hola');
        alert("Hola");
    }
}

function myFunction() {
    console.log('Hola');
    alert("Hola");
}

export default DireccionComponent;

但是这次我遇到了一个新的错误:

ReferenceError: myFunction is not defined[Learn More]

我发现一个类似的问题:如何处理dangerouslySetInnerHTML/常规html内部的标签的onClick事件,并尝试进一步调查,找到了这个评论,其中说从dangerouslySetInnerHTML中获取的事件不会以这种方式触发,并提供了文档链接。因此,当我直接从onclick方法中编写时,这似乎是一个解决方法,或者可能我没有以正确的方式执行,所以如果有更多经验的人可以尝试并澄清,那就太好了。

编辑4

我找到了一种显示 alert 的方法:

  • 将HTML写成字符串
  • 通过dangerouslySetInnerHTML设置HTML
  • 在React的componentDidMount()函数中使用纯JavaScript添加onclick函数。
  • 成功

然而,我们要做的是

  • 创建一些方法,例如:addTwoNumbers或类似的方法
  • 从HTML字符串内部发送一个onclickonClick,调用addTwoNumbers函数。
  • 每次需要添加一个新组件时重复这个过程,该组件需要使用addTwoNumbers方法,只需编写HTML代码,将其保存在数据库中,然后检索它并使用它。

例如:

...
    <label onClick={myFunction}> My Label </label>
...

或者正如我之前所说:

...
    <label onClick={addTwoNumbers}> My Label </label>
...

让我能够获取警报的代码如下:

import React, {Component} from 'react';
import Parser from 'html-react-parser';
import {render} from 'react-dom';

var ReactDOMServer = require('react-dom/server');
var HtmlToReactParser = require('html-to-react').Parser;

let htmlInput = ''
            //+ '<div>'
            +       '<center><label className="title">Widget</label></center>'
            +       '<span className="ui-float-label">'
            +           '<label htmlFor="float-input" id="myLabel">Hello2</label>'
            +       '</span>'
            //+     '</div>';

var htmlToReactParser = new HtmlToReactParser();
var reactElement = htmlToReactParser.parse(htmlInput);

class DireccionComponent extends Component {
    constructor(props) {
        super (props);

        this.myFunction = this.myFunction.bind(this);
    }

    render() {
        return (
            <div dangerouslySetInnerHTML={{__html: htmlInput}}>

            </div>
        )
    }

    componentDidMount() {
        console.log('DONE');

        let myLabel = document.getElementById("myLabel");

        myLabel.onclick = function() {
            alert();
            console.log('clicked');
        }
        console.log(myLabel);
    }

    myFunction() {
        console.log('Hola');
        alert("Hola");
    }
}

function myFunction() {
    console.log('Hola');
    alert("Hola");
}

export default DireccionComponent;

考虑到这一点,你知道其他实现我想做的事情的方法吗?


你发布的错误似乎与你发布的代码不符 (onclick vs onClick -- 你的代码中已经有后者了)。 - J. Titus
@J.Titus 抱歉,在你编辑评论之前我已经发布了我的回复。实际上很奇怪,但我尝试了 onclickonClick 两种写法,结果都与上面的输出相同。 - Frakcool
哦,那很有趣。我猜你已经检查过缓存,确保它没有捣鬼了? :) - J. Titus
你说得对,我已经确认过了。 - Frakcool
你想让我发布 App.jspackage.json 吗? - Frakcool
显示剩余2条评论
4个回答

7
解决方案有些不太正式,需要在codesandbox中复制粘贴代码。
class App extends Component {
  constructor(props) {
    super(props);
    this.myfunc = this.myfunc.bind(this);
  }
  componentDidMount() {
    window.myfunc = this.myfunc;
  }
  myfunc() {
    console.log("from myfunc");
  }
  componentWillUnmount() {
    window.myfunc = null;
  }
  render() {
    let inputhtml = "<a onclick=window.myfunc()>HelloWorld</a>";
    return (
      <div style={styles}>
        <div dangerouslySetInnerHTML={{ __html: inputhtml }} />
      </div>
    );
  }
}

这会在任何点击之前执行函数,而不是在用户点击时执行。 - Frakcool
抱歉,我无法重现它(在点击之前执行函数并且不在所有点击上被点击)。请检查 codesandbox 的链接 https://codesandbox.io/s/61qn31lwyr 并告诉我这个问题是否仍然存在。 - Rahil Ahmad
你能在回答中贴出来自沙盒的完整代码和链接吗?有时它们会被删除,保留下来是非常值得的。 - Frakcool

0

我不知道它是否适用于您的特定情况,但一个想法是根本不解析HTML。您可以告诉webpack为不同的文件集生成单独的包。现在,我从未这样做过,但我已经读了好几次,例如this

然后,您可以拥有一个main.bundle.js,其中包含所有不更改的站点部分。此外,为每个可能更改的小部件创建widget1.bundle.js等等。在index.html上导入所有这些。然后,配置您的Web服务器永远不要缓存较小的包(widget1等)。每当用户进入网站时,它将再次下载这些小部件,有效地更新它们。如果您想要更加花哨,可以提供具有每个小部件当前版本的API,并在小部件包上放置一个额外的字段以获取版本,以便您可以定期检查组件是否最新,以防用户长时间未重新加载页面。如果发现过时的组件,只需强制重新加载,浏览器将确保获取最新版本。

我知道这条路与你所走的完全不同,但如果它适用于你的特定情况,我相信它会比手动解析HTML和自己处理所有请求更简单、更易实现、更稳定、更易维护,总体上是一个更好的解决方案。


-1
为什么不使用浏览器DOMParser API将HTML解析为脱离屏幕/分离的DOM,然后遍历该DOM并使用React.createElement()来构建React的DOM呢?就像这样
let str = "... your HTML ..."

function traverse(node) {
  let children = []
  for (let i = 0; i < node.children.length; i++)
    children.push(traverse(node.children.item(i)))
  return React.createElement(node.localName, null, children)
  // or maybe use the following instead (React's API doc
  // isn't very clear about this):
  // return React.createElement(node.localName, null, ...children)
}

let reactElementForStr =
  traverse(new DOMParser().parseFromString(str, 'text/html'))

请注意,我只对React的内部工作有一些了解,并且没有进行过任何测试。

不建议使用DOMParser,因为它会对性能产生巨大影响。 - karthik

-1
最简单的方法是使用危险的innerHTML设置。
function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;

但这是有代价的,将HTML字符串动态嵌入模板会导致安全漏洞,这就是为什么它有那个奇怪的语法__html和一个可怕的名字dangerouslySetInnerHTML。如果您使用此功能,请确保对输入参数进行清理以防止任何XSS攻击。或者您可以使用iframe

请查看下面的完整实现(推荐) https://codepen.io/varmakarthik12/pen/zaOzPw


虽然你的例子渲染了一个HTML,但我想在字符串内部调用HTML中的函数。如果可能的话,你能把这个功能加到你的代码示例中吗? - Frakcool
那么我非常确定你正在使用dangerouslySetInnerHTML,它不接受任何返回类型。你是否尝试过检查https://codepen.io/varmakarthik12/pen/zaOzPw,在那里我甚至有一个按钮? - karthik
是的,它可以工作,但正如我所说,功能将会更大,调用一个function。这是可能的吗?我的意思是类似于onclick='myFunction()'myFunction = function() { console.log('Hello'); }或类似的东西。 - Frakcool
现在请检查 https://codepen.io/varmakarthik12/pen/zaOzPw?editors=1010,看看有什么变化。像第70行那样给元素一个ID,并检查_updateIframe方法,在其中调用getImplentation方法。这样,您甚至可以从组件内部调用方法,而不必使用全局实例 :) - karthik
现在我们到达了Edit 4,仍需要调用document.getElementById并将onclick添加到文件中。我们希望能够无需使用它们进行调用,只需使用 onclick=myFunction() 即可完成。如果不可能的话,那么我将不得不考虑另一种策略。 - Frakcool
显示剩余6条评论

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