如何在reactjs中将onclick事件添加到由dragonallysetInnerHtml呈现的字符串中?

时间:2019-01-09 12:07:17

标签: javascript html reactjs onclick innerhtml

我有一个字符串,即

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

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

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

我希望能够将onclick事件添加到<b>标记中,以对单击执行状态操作。

潜在的问题是我有一个应该呈现API传递的函数的函数。该API将发送字符串“为订单ID 123 收到的款项”,或者可以是我无法控制的任何字符串。后来,我有了一个要求,其中加粗的项目必须是可单击的,以便执行某些操作。我没有其他解决办法。

我该如何实现?

3 个答案:

答案 0 :(得分:1)

注意事项::听起来像X/Y problem,应该以不同的方式解决潜在的问题(无论是什么问题),这样您就不必添加任何点击通过dangerouslySetInnerHTML创建的DOM元素的处理程序(理想情况下,根本不需要通过dangerouslySetInnerHTML创建DOM元素)。但是,请回答您提出的问题: (您已经澄清了用例;下面的解决方案#1适用,并且不是很差的做法。)

我认为您不能直接这样做。我能想到的两种解决方案:

  1. div上使用委派事件处理程序:在div上添加单击处理程序,但是只有在单击通过b元素时才采取措施。

  2. ref上使用div,然后将点击处理程序挂接到componentDidMountcomponentDidUpdate中(在其中找到b元素divquerySelector或类似的名称),如下所示:

这里是#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的位置(而不是使用带有箭头功能的属性;原因:12):

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

实时示例:

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>

Refs是一个“逃生舱口”,可让您直接进行DOM访问。不要轻易使用裁判。通常,有更好的选择。

但再次:无论如何,我都会以不同的方式解决潜在的问题。

答案 1 :(得分:1)

react-html-parser可以将HTML字符串转换为React组件。

使用转换回调函数,您可以使用JSX标签更新HTML字符串中的任何标签,并添加任何属性和事件侦听器。

这是我的用法:

ReactHtmlParser(item.value, {
    transform: (node) => {
        if (node.name === 'a' && node.attribs && node.attribs.href) {
            const matched = node.attribs.href.match(/^activity\/([0-9]+)$/i);

            if (matched && matched[1]) { // activity id
                return <a
                        href={node.attribs.href}
                        onClick={(e) => {                                                            
                          e.preventDefault();                                                            
                          this.props.openActivityModal(matched[1]);
                        }}
                        >{node.children[0].data}</a>
            }
        }
    } 
})

答案 2 :(得分:0)

这是一种满足您需求的干净方法。通过根据<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生命周期函数中,然后从那里更改状态。