Как анализировать строку, содержащую 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 · Second'};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
Но это связано с ценой. Вставка HTML-строки в шаблон динамически вызывает нарушение безопасности, поэтому у нее есть этот странный синтаксис __html и страшное имя riskouslySetInnerHTML. Если вы используете это, убедитесь, что вы дезинфицируете свои входные параметры, чтобы предотвратить привлечение XSS. В качестве альтернативы вы можете использовать iframe
Проверьте полную реализацию снизу (рекомендуется) https://codepen.io/varmakarthik12/pen/zaOzPw