为什么将变量绑定到事件监听器回调会导致它被重新定义?这可以避免吗?

问题描述 投票:0回答:2

抱歉,如果这有点粗糙,我对 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
类中,并将其作为道具传递给组件,但同样的事情发生了 - 一旦我将变量绑定到它,它就会触发控制台日志两次。

所以,我的问题是,为什么会发生这种情况?如何才能达到这些要求?

欢迎所有建议,谢谢!

javascript reactjs callback addeventlistener
2个回答
2
投票

您已经说过该按钮是另一种事件源的替代品,因此我们不会担心附加这样的单击处理程序并不是您通常在 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>
    );
}

0
投票

这里的问题是,您在创建组件时创建了事件侦听器,但从未删除它。这意味着每次实例化该组件时,都会创建一个新的事件侦听器,因此将显示一个新的 console.log。

此外,你做事的方式很奇怪:

  1. 不要使用React类组件,推荐选择函数式组件;
  2. 为什么要使用 id 创建按钮,然后在组件内部获取按钮引用? React 的优点是您不必这样做,只需将组件插入到
    <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
属性内单击时必须调用的回调函数。

希望我有用!

© www.soinside.com 2019 - 2024. All rights reserved.