首页 > 解决方案 > useEffect 没有正确删除事件

问题描述

我面临一个问题,有一个<App/>组件呈现一个子组件,即<Button/>组件。该<Button/>组件有 2props个,一个是布尔值,另一个是函数。当用户单击按钮时将显示背景,背景只是div在用户单击时隐藏。

当 boolean 属性发生变化时,<Button/>组件会动态添加事件侦听器,并在用户单击文档上的任意位置时移除侦听器。

我用基于类的生命周期钩子成功地添加了这个功能,但是在用 React 钩子替换组件时遇到了问题。

我知道有一个名为的钩子useEffect可以替换 的行为,componentDidMount但是我的实现不是基于更改的道具删除侦听器。即使隐藏了背景,听众也无需单击按钮即可工作,以及为什么 linter 会抱怨。componentWillUnmountcomponentDidUpdateuseEffect

React Hook useEffect 缺少依赖项:“addEvents”和“removeEvents”。要么包含它们,要么删除依赖数组。(反应钩子/详尽的deps)。

如何修复此行为并使该组件像类<Button />组件一样?

类组件的功能:

// CSS styles for backdrop
const customStyles = {
  backgroundColor: "rgba(0,0,0,.5)",
  position: "absolute",
  top: 0,
  left: 0,
  right: 0,
  bottom: 0
};

// Button class component
class Button extends React.Component {
  // Return the function
  toggle = event => {
    return this.props.toggle(event);
  };

  // Attach Listener to the document object
  handleDocumentClick = event => {
    this.toggle(event);
  };

  // Add listeners to the document object
  addEvents = () => {
    ["click", "touchstart"].forEach(event =>
      // Event propogate from body(root) element to eventTriggered element.
      document.addEventListener(event, this.handleDocumentClick, true)
    );
  };

  // remove listeners from the document object
  removeEvents = () => {
    ["click", "touchstart"].forEach(event =>
      document.removeEventListener(event, this.handleDocumentClick, true)
    );
  };

  // Add or remove listeners when the prop changes
  manageProp = () => {
    if (this.props.open) {
      this.addEvents();
    } else {
      this.removeEvents();
    }
  };

  componentDidMount() {
    this.manageProp();
  }

  componentWillUnmount() {
    alert("Button cleanup");
    this.removeEvents();
  }

  componentDidUpdate(prevProps) {
    if (this.props.open !== prevProps.open) {
      this.manageProp();
    }
  }

  render() {
    return <button onClick={this.toggle}>Button</button>;
  }
}

// App class component
class App extends React.Component {
  state = {
    open: false
  };

  toggle = () => {
    alert("Button is clicked");
    this.setState({
      open: !this.state.open
    });
  };

  render() {
    return (
      <div className="app">
        {/* Class-based Button Component */}
        <Button open={this.state.open} toggle={this.toggle} />
        {/* Backdrop */}
        {this.state.open && <div style={customStyles} />}
      </div>
    );
  }
}

// Render it
ReactDOM.render(<App />, document.getElementById("root"));
<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>
<div id="root"></div>

带有反应钩子的组件:

import React, { useEffect } from "react";

const Button = props => {
  const toggle = event => {
    return props.toggle(event);
  };

  // Attach this to the document object
  const handleDocumentClick = event => {
    toggle(event);
  };

  // Add event listeners
  const addEvents = () => {
    ["click", "touchstart"].forEach(event =>
      document.addEventListener(event, handleDocumentClick, true)
    );
  };

  // Remove event listeners
  const removeEvents = () => {
    ["click", "touchstart"].forEach(event =>
      document.removeEventListener(event, handleDocumentClick, true)
    );
  };

  // Add or remove listeners based on the state changes
  const manageProp = () => {
    if (props.open) {
      addEvents();
    } else {
      removeEvents();
    }
  };

  // Mount, Unmount & DidUpdate
  useEffect(() => {
    manageProp();
    // Unmount
    return () => {
      alert("Button cleanup");
      removeEvents();
    };
  }, [props.open]);

  // Render it
  return <button onClick={toggle}>Button</button>;
};

export default Button;

沙盒链接

标签: reactjs

解决方案


useEffect每次任何依赖项更改时返回的函数。在您的情况下,该依赖项是props.open.

当您removeEvents从该函数调用时,您将由于为props.open真而添加两次事件。相反,您可以做的是在每次道具更改时删除事件:

useEffect(() => {
    // Add or remove listeners based on the state changes
    const manageProp = () => {
      if (props.open) {
        addEvents();
      } else {
        removeEvents();
      }
    };
    // mount
    manageProp();
    // unmount
    return () => {
      removeEvents();
    };
  }, [props.open]);

代码沙盒

只需移动addEventsremoveEvents进入useEffect即可摆脱警告


推荐阅读