Как добавить событие onclick в строку, отображаемую dangerouslysetInnerHtml в Reactionjs? - PullRequest
0 голосов
/ 09 января 2019

у меня есть строка, т.е.

let string= "Hello <b>Click here</b>";

render() {
return (<div dangerouslySetInnerHTML={this.createMarkup(value)}/>
}

createMarkup = value => { 
        return { __html: value };
 };

Я хотел бы иметь возможность добавлять событие onclick в тег <b> для выполнения манипуляций с состоянием при нажатии.

Основная проблема заключается в том, что у меня была функция, которая должна была отрисовывать все, что передается API. API будет отправлять строку «Деньги получены для идентификатора заказа 123 » или может быть любой строкой, которую я не могу контролировать. Позже я получил требование, чтобы элемент, который выделен жирным шрифтом, должен быть кликабельным, чтобы выполнять некоторые действия. У меня не было другого способа решить это.

Как мне этого добиться?

Ответы [ 2 ]

0 голосов
/ 09 января 2019

Вот чистый способ удовлетворить ваши потребности. Разделив строку в зависимости от тега <br>, вы можете получить отображаемый массив текста:

class BoldText extends React.Component {
	constructor(props) {
		super(props)

		this.state = {
			input: "Money received for order ID <b>123</b>, wow for real, so <b>cool</b> its insane"
		}
	}

	boldClick = ev => {
		console.log('clicked !')
	}

	render() {
		const { input } = this.state

		const a = input.split('</b>')


		const filter = /<b>.*<\/b>/
		const text = input.split(filter)
		const clickable = filter.exec(input)
//<b onClick={this.boldClick}></b>
		return (
			<div>
				<p>{a.map(t => {
					const [text, bold] = t.split('<b>')
					return <span>{text}<b onClick={this.boldClick}>{bold}</b></span>
				})}
				</p>
			</div>
		)
	}
}

ReactDOM.render(<BoldText />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<idv id='root'>

Это решение должно решить проблему, которую вы упомянули в комментариях к ответу выше. Вы можете поместить свой вызов API в функцию жизненного цикла componentDidMount и изменить свое состояние оттуда.

0 голосов
/ 09 января 2019

Предостережение: Это звучит как X / Y проблема , где основная проблема (какой бы она ни была) должна решаться по-другому, чтобы у вас не было добавить обработчик кликов к элементу DOM, созданному с помощью dangerouslySetInnerHTML (в идеале, чтобы вам вообще не приходилось создавать элементы DOM с помощью dangerouslySetInnerHTML). Но отвечая на вопрос, который вы задали: (Вы разъяснили вариант использования; приведенное ниже решение № 1 применимо и не является плохой практикой.)

Я не думаю, что вы можете сделать это напрямую. Два решения, которые я могу придумать:

  1. Используйте делегированный обработчик событий для div: добавьте обработчик щелчков для div, но затем выполняйте действие только в том случае, если щелчок прошел через элемент b.

  2. Используйте ref на div, а затем подключите обработчик щелчка вверх в componentDidMount и componentDidUpdate (поиск элемента b в div через querySelector или аналогичный ), что-то вроде этого:

Вот пример # 1:

<div onClick={this.clickHandler} dangerouslySetInnerHTML={this.createMarkup(string)}/>

... где clickHandler равно

clickHandler(e) {
    // `target` is the element the click was on (the div we hooked or an element
    // with in it), `currentTarget` is the div we hooked the event on
    const el = e.target.closest("B");
    if (el && e.currentTarget.contains(el)) {
        // ...do your state change...
    }
}

... или если вам нужно поддерживать старые браузеры без ParentNode#closest:

clickHandler(e) {
    // `target` is the element the click was on (the div we hooked or an element
    // with in it), `currentTarget` is the div we hooked the event on
    let el = e.target;
    while (el && el !== e.currentTarget && el.tagName !== "B") {
        el = el.parentNode;
    }
    if (el && el.tagName === "B") {
        // ...do your state change...
    }
}

... и где вы связываете clickHandler в конструкторе (вместо использования свойства с функцией стрелки; почему: 1 , 2 ):

this.clickHandler = this.clickHandler.bind(this);

Live Пример:

let string = "Hello <b>Click here</b>";
class Example extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            clicks: 0
        };
        this.clickHandler = this.clickHandler.bind(this);
    }

    clickHandler(e) {
        // `target` is the element the click was on (the div we hooked or an element
        // with in it), `currentTarget` is the div we hooked the event on
        // Version supporting older browsers:
        let el = e.target;
        while (el && el !== e.currentTarget && el.tagName !== "B") {
            el = el.parentNode;
        }
        if (el && el.tagName === "B") {
            this.setState(({clicks}) => ({clicks: clicks + 1}));
        }
        // Alternative for modern browsers:
        /*
        const el = e.target.closest("B");
        if (el && e.currentTarget.contains(el)) {
            this.setState(({clicks}) => ({clicks: clicks + 1}));
        }
        */
    }

    createMarkup = value => { 
        return { __html: value };
    };

    render() {
        const {clicks} = this.state;
        return [
            <div>Clicks: {clicks}</div>,
            <div onClick={this.clickHandler} dangerouslySetInnerHTML={this.createMarkup(string)}/>
        ];
    }
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Вот пример # 2, но не делайте этого, если A) Вы можете решить основную проблему отдельно, или B) # 1 работает:

let string = "Hello <b>Click here</b>";
class Example extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            clicks: 0
        };
        this.divRef = React.createRef();
        this.hooked = null;
        this.clickHandler = this.clickHandler.bind(this);
    }

    clickHandler() {
        this.setState(({clicks}) => ({clicks: clicks + 1}));
    }

    hookDivContents() {
        // Get the b element
        const b = this.divRef.current && this.divRef.current.querySelector("b");

        // No-op if it's not there or it's the same element we have hooked
        if (!b || b === this.hooked) {
            return;
        }

        // Unhook the old, hook the new
        if (this.hooked) {
            this.hooked.removeEventListener("click", this.clickHandler);
        }
        this.hooked = this.divRef.current;
        this.hooked.addEventListener("click", this.clickHandler);
    }

    componentDidMount() {
        this.hookDivContents();
    }

    componentDidUpdate() {
        this.hookDivContents();
    }

    createMarkup = value => { 
        return { __html: value };
    };

    render() {
        const {clicks} = this.state;
        return [
            <div>Clicks: {clicks}</div>,
            <div ref={this.divRef} dangerouslySetInnerHTML={this.createMarkup(string)}/>
        ];
    }
}

ReactDOM.render(
    <Example />,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Ссылки - это «аварийный люк», дающий вам прямой доступ к DOM. Не используйте ссылки легкомысленно; как правило, есть лучший выбор.

Но опять же: Я бы решил основную проблему, какой бы она ни была, по-другому.

...