抱歉,如果这有点粗糙,我对 React 还比较陌生。这甚至可能更像是一个 JavaScript 问题。
我正在尝试添加一个事件侦听器,该事件侦听器将在组件内触发回调。该组件可以多次出现在页面上,使用下面的代码,当单击
#btn
时,console.log
将输出一次 - 我可以根据需要添加任意多个 <MyComponent />
,并且日志只会输出一次 - 根据需要。
const callback = (e) => {
console.log('callback happened!!', e.type);
}
const MyComponent = () => {
const btn = document.getElementById('btn');
if (btn) {
const name = 'Bob';
btn.addEventListener('click', callback);
}
return (
<div>
<p>Hi from my component!</p>
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<MyComponent />
<p>...</p>
<MyComponent />
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
<div id="btn">button</div>
我遇到的问题是,如果我尝试使用绑定(例如
name
)将变量(btn.addEventListener('click', callback.bind(null, name));
)传递给回调函数,当我单击按钮时,输出将被记录两次 - 不是我想要的想要发生!我需要 name
变量和事件对象在 callback
函数中可用。
我应该澄清,在上面的示例中,我在按钮上使用了单击侦听器,但实际情况将侦听从其他东西发出的事件。
我尝试将回调函数移动到
App
类中,并将其作为道具传递给组件,但同样的事情发生了 - 一旦我将变量绑定到它,它就会触发控制台日志两次。
所以,我的问题是,为什么会发生这种情况?如何才能达到这些要求?
欢迎所有建议,谢谢!
您已经说过该按钮是另一种事件源的替代品,因此我们不会担心附加这样的单击处理程序并不是您通常在 React 中执行此操作的方式。我假设另一个事件源位于 React 树之外。
您的组件函数在每次需要渲染组件时都会被调用,这可以多次。每次函数运行时,您的代码都会添加一个处理程序。当您直接使用
callback
执行此操作时,每次都是相同的函数,因此不会添加它(因为 addEventListener
不会将同一事件的相同函数添加到同一事件目标,即使您调用它反复)。但是,当您使用 bind
时,您每次都会创建一个 new 函数,因此 addEventListener
会在每次渲染时添加这些新函数。
相反,仅在安装组件时进行设置。另外,在卸载组件时将其移除。您可以通过
useEffect
: 来做到这一点
const MyComponent = () => {
useEffect(() => {
const btn = document.getElementById("btn");
if (btn) {
const name = "Bob";
const handler = callback.bind(null, name);
// Or: `const handler = (event) => callback(event, name);`
btn.addEventListener("click", handler);
return () => { // This function is called to clean up
btn.removeEventListener("click", handler);
};
}
}, []); // <== Empty array means "only on mount"
return (
<div>
<p>Hi from my component!</p>
</div>
);
}
这里的问题是,您在创建组件时创建了事件侦听器,但从未删除它。这意味着每次实例化该组件时,都会创建一个新的事件侦听器,因此将显示一个新的 console.log。
此外,你做事的方式很奇怪:
<div id="btn">button</div>
所在的位置即可。我将向您建议解决此问题的两种解决方案,第一个使用类组件,以便更接近您的解决方案(即使不建议再使用它们),另一个使用功能组件:
类组件:
import React from "react"
import ReactDOM from "react-dom"
class MyComponent extends React.Component {
handleClick = (name, e) => {
console.log("callback happened!!", e.type, name)
}
render() {
const { name } = this.props
return (
<div>
<p onClick={(e) => this.handleClick(name, e)}>Hi from my component!</p>
</div>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
<MyComponent name="Bob" />
<p>...</p>
<MyComponent name="Alice" />
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector("#app"))
功能组件:
import React from "react"
import ReactDOM from "react-dom"
function MyComponent({ name }) {
const handleClick = (e) => {
console.log("callback happened!!", e.type, name)
}
return (
<div>
<p onClick={(e) => handleClick(e)}>Hi from my component!</p>
</div>
)
}
function App() {
return (
<div>
<MyComponent name="Bob" />
<p>...</p>
<MyComponent name="Alice" />
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#app"))
请注意,在这两个解决方案中,我没有显式创建事件侦听器,但我附加了在元素的
onClick
属性内单击时必须调用的回调函数。
希望我有用!