如何将 onclick 事件添加到 reactjs 中由 dangerouslysetInnerHtml 呈现的字符串?
Posted
技术标签:
【中文标题】如何将 onclick 事件添加到 reactjs 中由 dangerouslysetInnerHtml 呈现的字符串?【英文标题】:How to add onclick event to a string rendered by dangerouslysetInnerHtml in reactjs? 【发布时间】:2019-06-04 04:58:57 【问题描述】:我有一个字符串,即
let string= "Hello <b>Click here</b>";
render()
return (<div dangerouslySetInnerhtml=this.createMarkup(value)/>
createMarkup = value =>
return __html: value ;
;
我想要的是能够向<b>
标签添加一个 onclick 事件,以便在点击时执行状态操作。
根本的问题是我有一个函数应该渲染 API 传递的任何内容。 API 会发送一个字符串'Money received for order ID 123',或者可以是我无法控制的任何字符串。后来,我得到一个要求,加粗的项目必须是可点击的,以便执行一些操作。我没有其他办法解决。
我怎样才能做到这一点?
【问题讨论】:
你为什么要这样做?用例是什么?好像是X/Y problem;如果可能的话,最好以不同的方式解决根本问题。 在this.createMarkup(value)
中的value
应该是string
吗?
你不能那样做。通过dangerouslySetInnerHTML
设置HTML,您将离开React 作用域,您将无法回调它,即使您让它工作,它也会很丑陋并且可能有风险。如果你想让 React 处理某些事情,请使用 React。如果你想让你的 <b>
标签更新状态,那么,使用 React 正确创建它。
在反应返回的元素应该至少有一个父元素。我会将“字符串”拆分为两个字符串,并且 元素应该作为反应元素而不是字符串。
【参考方案1】:
警告:这听起来像是X/Y problem,其中潜在的问题(无论是什么)应该以不同的方式解决,这样您就不必添加点击处理程序通过 (您已经阐明了用例;下面的解决方案#1 适用并且不是不好的做法。)dangerouslySetInnerHTML
创建的DOM 元素(理想情况下,您根本不必通过dangerouslySetInnerHTML
创建DOM 元素)。但是回答您提出的问题:
我认为你不能直接这样做。我能想到的两个解决方案:
在div
上使用委托事件处理程序:在div
上添加一个点击处理程序,但只有在点击通过b
元素时才执行操作。
在div
上使用ref
,然后将点击处理程序挂接到componentDidMount
和componentDidUpdate
(通过querySelector
或类似方法在div
中找到b
元素),大致如下:
这是#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);
现场示例:
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。不要轻易使用 refs;通常,会有更好的选择。
但又一次:我会以不同的方式解决根本问题,无论它是什么。
【讨论】:
虽然这种情况确实应该以另一种方式解决,但我不认为使用refs
是给 React 初学者的好建议,因为它被认为是一种不好的做法
@Treycos - Refs 对于他们的用途来说并不是坏习惯。但正如我在对该问题的评论中所说,以及答案的开始(和现在结束),无论潜在问题是什么,几乎可以肯定它应该以不同的方式解决。
@Treycos - 有一个 Doh! 时刻并添加了一个更好的解决方案。 :-)
哦,哇,代码太多了。虽然这完美地回答了这个问题,但我认为这不会帮助 OP 理解类似 React 的架构并使他更加困惑。我犹豫是否要发布一种更像 React 的方式来执行此操作,但这可能违反 SO 的逻辑。
@CKA - 我的直觉反应是双重的:A)我想看看是否有可能从服务器而不是 HTML 获取 信息,所以这不会上来。如果不可能,那么 B)我会使用委托事件处理程序(上面的解决方案#1)。对于所描述的情况,这是相当干净的。但我认为一个新问题概述了这种情况并询问 React 处理它的方式是什么,这将是一个很好的问题,我很想看到它的答案。 (但不要改变这个问题,因为一旦他们以一种使答案无效的方式收到答案就改变问题并不是 SO 的工作方式。)快乐编码!【参考方案2】:
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>
)
【讨论】:
【参考方案3】:这是满足您需求的一种简洁方法。通过根据 <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'>
此解决方案应该可以解决您在上面答案的 cmets 中提到的问题。您可以将 API 调用放在 componentDidMount
生命周期函数中,然后从那里更改您的状态。
【讨论】:
强烈建议不要对 HTML 字符串进行简单的操作,这对于生产使用来说太脆弱了。 (现在,一个真正解析 HTML 的解决方案——例如,DOMParser——可能是一种可行的方法,尽管在这里似乎没有必要。) 好吧,如果他的字符串包含段落、div 和与之相关的所有内容,当然,这是一个糟糕的解决方案。但只要他的数据是由文本和<b>
标签组成的,这个过程应该可以完美运行以上是关于如何将 onclick 事件添加到 reactjs 中由 dangerouslysetInnerHtml 呈现的字符串?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 reactjs 中使用 onclick 事件渲染子组件?