Как анализировать строку, содержащую HTML, чтобы реагировать на компоненты

Я использую эту библиотеку: html-to-react поскольку я обнаружил, что можно "скомпилировать" HTML как String для React Components.

Мы делаем систему на основе виджетов, и со временем они могут меняться (например, add/remove/update/etc), поэтому мы планируем отправить виджет HTML из базы данных на передний план, который выполняется с помощью React.

Я успешно сделал простой виджет с использованием HTML, теперь я пытаюсь, чтобы этот виджет отвечал на события щелчка, поэтому я подумал о добавлении onclick=method() из HTML или onClick={method} из React. Однако я не смог получить желаемый результат, поскольку получаю эти ошибки в консоли браузера.

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-кода для устранения компонентов? Если да, то как я могу это сделать? Или что я делаю неправильно?


редактировать

Это не обязательно должна быть конкретная библиотека, которую я использую, если есть еще один, с которым вы работали и что-то подобное, я открыт для получения этих рекомендаций (обратите внимание, что я не прошу программного обеспечения или библиотеки, но как вызвать метод onClick внутри String, содержащий мой HTML или обходной путь)


Изменить 2

Попробовав что-то, я обнаружил, что удаление этой строки:

var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement);

Удаляет одно из сообщений. Библиотека использует этот элемент для проверки того, совпадают ли как текущий 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 в этом ответе

Затем я попытался выполнить этот ответ, разместив alert внутри своего HTML следующим образом:

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

Я нашел аналогичный вопрос: обращаясь с помощью этой проблемы к методу <a> тега внутри опасногоSetInnerHTML/регулярного html и попытался изучить немного больше и нашел этот комментарий, в котором говорится, что события из dangerouslySetInnerHTML не будут запускаться таким образом и ссылки на документы.

Таким образом, хотя это, казалось, было обходным путем, когда я получил alert при написании непосредственно из метода onclick, или, вероятно, я не делал этого правильно, поэтому, если кто-то с большим опытом может попробовать и уточнить, было бы здорово,


Редактировать 4

Я нашел способ показать alert таким образом:

  • Напишите HTML как строку
  • Установите HTML через dangerouslySetInnerHTML
  • В функции componentDidMound() из React используйте чистый Javascript, чтобы добавить к нему функцию onclick.
  • успех

Однако то, что мы пытаемся сделать, это:

  • Создайте несколько методов, например: addTwoNumbers или что-то в этом роде
  • Отправьте onclick или onClick из строки HTML, вызывающей функцию addTwoNumbers.
  • Повторяйте каждый раз, когда нам нужно добавить новый компонент, который должен использовать метод 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;

Имея это в виду, знаете ли вы какой-либо другой способ делать то, что я пытаюсь сделать?

Ответы

Ответ 1

Решение немного взломано, скопируйте пасту в 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>
    );
  }
}

Ответ 2

Я не знаю, будет ли это работать для вашего конкретного случая, но идея состоит в том, чтобы вообще не анализировать HTML. Вы можете сообщить webpack о создании отдельных пакетов для разных наборов файлов. Теперь, я никогда не делал этого, но я читал несколько раз, что вы можете; например, это.

Тогда у вас может быть main.bundle.js, у которого есть все части вашего сайта, которые не меняются. Кроме того, создайте widget1.bundle.js и т.д. Для каждого виджета, который может измениться. Импортируйте все те из вашего index.html. Затем настройте ваш веб-сервер, чтобы никогда не кэшировать меньшие пакеты (widget1 и т.д.). Всякий раз, когда пользователь входит на веб-сайт, он снова загружает эти виджеты, эффективно обновляя их. Если вы хотите быть экстраординарным, вы можете предоставить API с текущей версией каждого виджета и поместить дополнительное поле в комплект виджета с версией, чтобы вы могли периодически проверять, обновлены ли компоненты в событие, которое пользователь не перезагрузил страницу за какое-то время. Если вы обнаружите устаревший компонент, просто запустите перезагрузку, и браузер обязательно заберет последнюю версию.

Я знаю, что это совершенно другой путь от того, с которым вы идете, но если это хорошо для вашего конкретного случая, я считаю, что это будет проще, проще реализовать и гарантировать стабильность, более удобную поддержку и в целом лучшее решение, чем ручной анализ html и обрабатывая все запросы самостоятельно.

Ответ 3

Почему бы не использовать API-интерфейс DOMParser браузера для анализа HTML-кода в автономном DOM- React.createElement(), а затем React.createElement() этот 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 API doc
  // isn't very clear about this):
  // return React.createElement(node.localName, null, ...children)
}

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

Имейте в виду, я имею только мимолетное знание Внутренних Работ Реакта, и не испытал это вообще.

Ответ 4

Самый простой способ сделать это - Dangerously Set innerHTML

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

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

Но это связано с ценой. Вставка HTML-строки в шаблон динамически вызывает нарушение безопасности, поэтому у нее есть этот странный синтаксис __html и страшное имя riskouslySetInnerHTML. Если вы используете это, убедитесь, что вы дезинфицируете свои входные параметры, чтобы предотвратить привлечение XSS. В качестве альтернативы вы можете использовать iframe

Проверьте полную реализацию снизу (рекомендуется) https://codepen.io/varmakarthik12/pen/zaOzPw