简体   繁体   English

如何将包含HTML的String解析为React Components

[英]How to parse a String containing HTML to React Components

I'm using this library: html-to-react as I found that it was possible to "compile" HTML as a String to React Components. 我正在使用这个库: html-to-react因为我发现可以将HTML“编译”为一个字符串来反应组件。

We're doing a system based on widgets and they might change over time (ie add / remove / update / etc), so we're planning to send the widget HTML from the database to the front which is done using React. 我们正在做一个基于小部件的系统,它们可能会随着时间而改变(即添加/删除/更新/等),因此我们计划将小部件HTML从数据库发送到前端,这是使用React完成的。

I've successfully done a simple widget using HTML, now I'm trying this widget to respond to click events, so I thought on adding onclick=method() method from HTML or onClick={method} from React. 我已经使用HTML成功完成了一个简单的小部件,现在我正在尝试使用这个小部件来响应点击事件,所以我想从HTML中添加onclick=method()方法或者从React添加onClick={method} However I've not been able to get the desired output as I'm getting these errors in my browser's console. 但是我无法获得所需的输出,因为我在浏览器的控制台中收到了这些错误。

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)

The code I'm using is the following: 我正在使用的代码如下:

App.js 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 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 的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"
  }
}

Is it possible to get an alert or something similar using the above library to parse HTML to React Components? 是否可以使用上面的库来获取alert或类似内容来将HTML解析为React组件? If so, how could I do it? 如果是的话,我怎么能这样做? Or what am I doing wrong? 或者我做错了什么?


Edit 编辑

It doesn't have to be specifically the library I'm using, if there's another one you've worked with and done something similar, I'm open to receive this recommendations (Note, I'm not asking for a software or library, but how to call the onClick method inside the String containing my HTML or a workaround) 它不一定是我正在使用的库,如果有另一个你曾经使用并做过类似的事情,我愿意接受这个建议(注意,我不是要求一个软件或库,但如何在包含我的HTML或变通方法的String中调用onClick方法)


Edit 2 编辑2

After trying something I found that removing this line: 在尝试了一些东西后,我发现删除了这一行:

var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement);

Gets rid of one of the messages. 删除其中一条消息。 The library uses that element to test if both the current HTML and the parsed one are the same. 该库使用该元素来测试当前HTML和解析的HTML是否相同。 I didn't needed it for my case but that still leaves the current error in the console: 我没有为我的情况需要它,但仍然在控制台中留下当前错误:

 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)

Edit 3 编辑3

After some investigation I found dangerousSetInnerHTML in this answer 经过一番调查后,我在这个答案中找到了dangerousSetInnerHTML

Then I tried to follow this answer an placing an alert inside my HTML as follows: 然后我尝试按照这个答案在我的HTML中放置一个alert ,如下所示:

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

And it triggered the alert , however if I declared myFunction like in the code below (notice I tried to declare it as a function outside the class, inside it and also as var myFunction = function() { ... } all together and separated): 并且它触发了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;

But this time I get a new error: 但这次我收到一个新错误:

ReferenceError: myFunction is not defined[Learn More]

I found a similar question: Handling onClick of a <a> tag inside of dangerouslySetInnerHTML/regular html with this problem, and tried to investigate a little more and found this comment which says that the events from the dangerouslySetInnerHTML won't be triggered this way and links to the docs . 我发现了一个类似的问题:使用此问题处理危险的SetInnerHTML /常规html中的<a>标签onClick ,并尝试进行更多调查并发现此评论说明来自dangerouslySetInnerHTML的事件不会以这种方式触发和链接到文档

So, while this seemed to be a workaround when I got an alert when written directly from the onclick method, or probably I didn't do it in the correct way, so if someone with more experience could try it and clarify, would be great. 所以,虽然当我直接从onclick方法编写alert时,这似乎是一种解决方法,或者我可能没有以正确的方式执行,所以如果有经验的人可以尝试并澄清,那将是很好的。


Edit 4 编辑4

I found a way to show an alert in this way: 我找到了一种以这种方式显示alert的方法:

  • Write the HTML as a String 将HTML写为字符串
  • Set the HTML through dangerouslySetInnerHTML 通过dangerouslySetInnerHTML设置HTML
  • On componentDidMound() function from React use pure Javascript to add onclick function to it. 在React的componentDidMound()函数中使用纯Javascript为它添加onclick函数。
  • Success 成功

However, what we're trying to do is : 但是, 我们要做的是

  • Create some methods, for example: addTwoNumbers or something like that 创建一些方法,例如: addTwoNumbers或类似的东西
  • Send an onclick or onClick from within the HTML String calling addTwoNumbers function. 从调用addTwoNumbers函数的HTML字符串中发送onclickonClick
  • Repeat everytime we need to add a new component that needs to use the addTwoNumbers method and just have to write the HTML for it, save it on the database and retrieve it and then just use it. 每次我们需要添加一个需要使用addTwoNumbers方法的新组件时重复,只需要为它编写HTML,将其保存在数据库中并检索它然后只使用它。

Something like: 就像是:

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

Or as I said above: 或者正如我上面所说:

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

The code that allows me to get an alert is the following: 允许我获取alert的代码如下:

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;

With this in mind, do you know any other way to do what I'm trying to do? 考虑到这一点,您是否知道其他任何方式来做我想做的事情?

The solution is a bit hacky , copy paste in codesandbox 解决方案有点hacky,在codeandbox中复制粘贴

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>
    );
  }
}

I don't know if it would work for your particular case, but an idea is not to parse HTML at all. 我不知道它是否适用于您的特定情况,但一个想法是根本不解析HTML。 You can tell webpack to generate separated bundles for different sets of files. 您可以告诉webpack为不同的文件集生成单独的包。 Now, I've never done this, but I've read several times that you can; 现在,我从来没有这样做,但我已经多次读过你可以做到的; for example, this . 例如, 这个

Then you can have a main.bundle.js that has all the parts of your site that do not change. 然后你可以有一个main.bundle.js,它包含你网站的所有部分都没有改变。 Also, create a widget1.bundle.js and so on for every widget that can change. 此外,为每个可以更改的窗口小部件创建一个widget1.bundle.js等。 Import all those on your index.html. 导入index.html上的所有内容。 Then, configure your webserver to never cache the smaller bundles (widget1, etc). 然后,将您的Web服务器配置为永远不会缓存较小的包(widget1等)。 Whenever the user enters the website, it will download those widgets again, effectively updating them. 每当用户进入网站时,它将再次下载这些小部件,有效地更新它们。 If you want to be extra fancy, you can provide an API with the current version of each widget, and put an extra field on the widget bundle with the version, so that you can periodically check if the components are up to date, in the event that the user haven't reloaded the page in a while. 如果你想要更加花哨,你可以提供一个API与每个小部件的当前版本,并在小部件包上添加一个额外的字段与版本,以便您可以定期检查组件是否是最新的,在用户有一段时间没有重新加载页面的事件。 If you discover an outdated component, just force reload, and the browser will make sure to fetch the latest version. 如果您发现过时的组件,只需强制重新加载,浏览器将确保获取最新版本。

I know this is totally different path from the one you are going, but if it's good for your particular case, I believe it will be simpler, easier to implement and guarantee stability, more maintainable and overall a better solution than hand parsing the html and handling all the requests yourself. 我知道这与你要去的路径完全不同,但如果它对你的特定情况有好处,我相信它会更简单,更容易实现并保证稳定性,更易于维护,并且整体上比手工解析html更好的解决方案自己处理所有请求。

Why not use the browser DOMParser API to parse your HTML into an off-screen/detached DOM, then traverse that DOM and use React.createElement() to build up React's DOM? 为什么不使用浏览器DOMParser API将HTML解析为屏幕外/分离的DOM,然后遍历该DOM并使用React.createElement()来构建React的DOM? Something like 就像是

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'))

Mind you, I have only a passing knowledge of React's inner workings, and haven't tested this at all. 请注意,我对React的内部工作只有一点知识,并且根本没有对此进行过测试。

Easiest way of doing this would be Dangerously Set innerHTML 最简单的方法是使用Dangerously Set innerHTML

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

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

But this comes with a price, Embedding HTML string into the template dynamically causes a security violation thats why it has that weird syntax __html and a scary name dangerouslySetInnerHTML. 但这需要付出代价,将HTML字符串嵌入到模板中会动态导致安全违规,这就是为什么它具有奇怪的语法__html和一个可怕的名称dangerouslySetInnerHTML。 If you are using this make sure you sanitize your input params to prevent any XSS attracts. 如果您正在使用此功能,请确保您清理输入参数以防止任何XSS吸引。 Alternatively you can use an iframe 或者,您可以使用iframe

Check the complete implementation from below pen (Recommended) https://codepen.io/varmakarthik12/pen/zaOzPw 从下面的笔中检查完整的实现(推荐) https://codepen.io/varmakarthik12/pen/zaOzPw

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM