如何将 onclick 事件添加到 reactjs 中 dangerouslysetInnerHtml 呈现的字符串?
How to add onclick event to a string rendered by dangerouslysetInnerHtml in reactjs?
我有一个字符串,即
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',或者可以是我无法控制的任何字符串。后来接到一个需求,加粗的item必须是可以点击的,才能进行一些操作。我没有任何其他方法可以解决它。
我怎样才能做到这一点?
警告: 这听起来像一个 X/Y problem,其中潜在的问题(不管是什么)应该以不同的方式解决,这样你就不会不必向通过 dangerouslySetInnerHTML
创建的 DOM 元素添加点击处理程序(理想情况下,您根本不必通过 dangerouslySetInnerHTML
创建 DOM 元素)。但是回答你问的问题: (你已经阐明了用例;下面的解决方案 #1 适用并且不是不好的做法。)
我认为您不能直接这样做。我能想到的两个解决方案:
在 div
上使用委托事件处理程序:在 div
上添加点击处理程序,但仅当点击通过 b
时才采取行动元素.
在 div
上使用 ref
,然后在 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);
实例:
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 是一个 "escape hatch",让您可以直接 DOM 访问。不要轻易使用 refs;通常,有更好的选择。
但是再说一遍:我会以不同的方式解决根本问题,不管它是什么。
这里有一种简洁的方法可以满足您的需求。通过根据 <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
生命周期函数并从那里更改状态。
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>
}
}
}
})
您可以将父标签设为 <form>
并设置 onClick="SomeFunction()"
.
从具有 HTML 字符串的子标记中,设置 type="button"
。
let string= "Hello <b type='button'>Click here</b>";
render() {
return (
<form onClick={SomeFunction} dangerouslySetInnerHTML =
{this.createMarkup(value)}/>
}
createMarkup = value => {
return { __html: value };
};
我有一个字符串,即
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',或者可以是我无法控制的任何字符串。后来接到一个需求,加粗的item必须是可以点击的,才能进行一些操作。我没有任何其他方法可以解决它。
我怎样才能做到这一点?
警告: 这听起来像一个 X/Y problem,其中潜在的问题(不管是什么)应该以不同的方式解决,这样你就不会不必向通过 (你已经阐明了用例;下面的解决方案 #1 适用并且不是不好的做法。)dangerouslySetInnerHTML
创建的 DOM 元素添加点击处理程序(理想情况下,您根本不必通过 dangerouslySetInnerHTML
创建 DOM 元素)。但是回答你问的问题:
我认为您不能直接这样做。我能想到的两个解决方案:
在
div
上使用委托事件处理程序:在div
上添加点击处理程序,但仅当点击通过b
时才采取行动元素.在
div
上使用ref
,然后在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);
实例:
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 是一个 "escape hatch",让您可以直接 DOM 访问。不要轻易使用 refs;通常,有更好的选择。
但是再说一遍:我会以不同的方式解决根本问题,不管它是什么。
这里有一种简洁的方法可以满足您的需求。通过根据 <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
生命周期函数并从那里更改状态。
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>
}
}
}
})
您可以将父标签设为 <form>
并设置 onClick="SomeFunction()"
.
从具有 HTML 字符串的子标记中,设置 type="button"
。
let string= "Hello <b type='button'>Click here</b>";
render() {
return (
<form onClick={SomeFunction} dangerouslySetInnerHTML =
{this.createMarkup(value)}/>
}
createMarkup = value => {
return { __html: value };
};