【问题标题】:How to parse a String containing HTML to React Components如何将包含 HTML 的字符串解析为 React 组件
【发布时间】:2018-10-29 10:07:53
【问题描述】:

我正在使用这个库: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"
  }
}

是否可以获得alert 或使用上述库将 HTML 解析为 React 组件的类似内容?如果是这样,我该怎么做?还是我做错了什么?


编辑

它不一定是我正在使用的库,如果有另一个你曾经使用过并做过类似的事情,我愿意接受这个建议(注意,我不是要求软件或库,但如何在包含我的 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

经过一番调查,我在this answer 中找到了dangerousSetInnerHTML

然后我尝试按照this answer 在我的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]

我发现了一个类似的问题:Handling onClick of a <a> tag inside of dangerouslySetInnerHTML/regular html 有这个问题,并尝试进行更多调查,发现this comment 表示来自dangerouslySetInnerHTML 的事件不会以这种方式触发并链接到@ 987654327@.

所以,虽然当我直接从 onclick 方法写入 alert 时,这似乎是一种解决方法,或者我可能没有以正确的方式执行此操作,所以如果有更多经验的人可以尝试它并澄清,会很棒。


编辑 4

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

  • 将 HTML 写为字符串
  • 通过dangerouslySetInnerHTML设置HTML
  • 在来自 React 的 componentDidMound() 函数上使用纯 Javascript 向其中添加 onclick 函数。
  • 成功

但是,我们正在尝试做的是

  • 创建一些方法,例如:addTwoNumbers 或类似的东西
  • 从调用addTwoNumbers函数的HTML字符串中发送onclickonClick
  • 每次我们需要添加一个需要使用 addTwoNumbers 方法的新组件时重复一遍,只需为其编写 HTML,将其保存在数据库中并检索它,然后就可以使用它。

类似:

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

或者就像我上面说的:

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

让我获得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;

考虑到这一点,你知道有什么其他方法可以做我想做的事吗?

【问题讨论】:

  • 您发布的错误似乎与您发布的代码不一致。 (onclick vs onClick——你的代码中已经有了后者)
  • @J.Titus 对不起,我在您对评论进行编辑之前发布了我的回复。是的,实际上这很奇怪,但我都试过了,onclickonClick,都给了我与上面相同的输出。
  • 哦,这很有趣。我假设您已经检查过以确保缓存没有欺骗您? :)
  • 你是对的,我已经确定了
  • 你要我发App.jspackage.json吗?

标签: javascript html reactjs parsing


【解决方案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 调用一个函数。如果可以的话,你能把它加到你的笔里吗?
  • 那么我很确定您正在使用不接受任何返回类型的危险SetInnerHTML。你试过检查codepen.io/varmakarthik12/pen/zaOzPw 我什至有一个按钮吗?
  • 是的,如果您检查问题中的 Edit 3,我已经说过我发现使用它可能会有所帮助。是的,我看到你有一个按钮Test,但点击它什么也没做。那个按钮是我想做的事情onclick
  • 您现在可以打开控制台检查它吗(刷新它我刚刚添加了 onclick 处理程序)。像第 70 行 JS 一样检查
  • 是的,它可以工作,但正如我所说,功能将会更大,它自己调用function。可能吗?我的意思是onclick='myFunction()'myFunction = function() { console.log('Hello'); } 之类的东西。
【解决方案2】:

解决方法有点hacky,复制粘贴到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>
    );
  }
}

【讨论】:

  • 这会在任何点击之前执行该函数,而不是在用户点击时执行。
  • 对不起,我无法重现它(点击之前的函数执行,并且没有在所有点击中被点击)..请检查代码和框codesandbox.io/s/61qn31lwyr的链接,如果这个问题仍然存在,请告诉我.
  • 您能否将沙箱中的完整代码发布到您的答案中?还有链接?有时它们会被删除,值得保留。
【解决方案3】:

为什么不使用浏览器 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,因为它对性能影响很大。
【解决方案4】:

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

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

我知道这与您要走的路完全不同,但如果它对您的特定情况有好处,我相信它会更简单,更容易实现并保证稳定性,更易于维护并且总体上比手动解析更好的解决方案html 并自己处理所有请求。

【讨论】:

    猜你喜欢
    • 2019-11-23
    • 1970-01-01
    • 1970-01-01
    • 2015-09-23
    • 2017-11-22
    • 2019-01-15
    • 1970-01-01
    • 2011-12-19
    • 1970-01-01
    相关资源
    最近更新 更多