使用事件监听器反应自定义下拉菜单

时间:2019-08-24 01:37:27

标签: javascript reactjs dropdown addeventlistener

我创建了一个Dropdown,当我在其外部单击时,下拉列表消失。我使用点击事件监听器来确定是否在下拉列表之外单击。

单击几下后,页面会变慢并崩溃。也许状态是循环呈现的,还是一次触发了太多事件?

该如何解决? 另外,还有更多的React方法来确定我是否在元素外部单击了吗? (而不是使用document.body事件监听器)

这是codepen:

const items = [
    {
        value: 'User1'
    },
    {
        value: 'User2'
    },
    {
        value: 'User3'
    },
    {
        value: 'User4'
    },
    {
        value: 'User5'
    }
];
    
class Dropdown extends React.Component {
  state = {
    isActive: false,
  }

  render() {
    const { isActive } = this.state;
    document.addEventListener('click', (evt) => {
        if (evt.target.closest('#dropdownContent')) {
          //console.warn('clicked inside target do nothing');
          return;
        }

        if (evt.target.closest('#dropdownHeader')) {
          //console.warn('clicked the header toggle');
          this.setState({isActive: !isActive});
        }

        //console.warn('clicked outside target');
        if (isActive) {
          this.setState({isActive: false});
        }
      });
    
      return (
          <div id="container">
              <div id="dropdownHeader">select option</div>
              {isActive && (
                <div id="dropdownContent">
                  {items.map((item) => (
                    <div id="item" key={item.value}>
                      {item.value}
                    </div>
                  ))}
                </div>
              )}
          </div>
      );
  };
}
  ReactDOM.render(
    <Dropdown items={items} />,
    document.getElementById('root')
  );
#container {
    position: relative;
    height: 250px;
    border: 1px solid black;
}

#dropdownHeader {
    width: 100%;
    max-width: 12em;
    padding: 0.2em 0 0.2em 0.2em;
    margin: 1em;
    cursor: pointer;
    box-shadow: 0 1px 4px 3px rgba(34, 36, 38, 0.15);
}

#dropdownContent {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 3em;
    width: 100%;
    max-width: 12em;
    margin-left: 1em;
    box-shadow: 0 1px 4px 0 rgba(34, 36, 38, 0.15);
    padding: 0.2em;
}

#item {
    font-size: 12px;
    font-weight: 500;
    padding: 0.75em 1em 0.75em 2em;
    cursor: pointer;
}
<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">
    <!-- This element's contents will be replaced with your component. -->
</div>

1 个答案:

答案 0 :(得分:1)

关于您所遇到的事情,有一个非常简单的解释。 :)

我能弄清楚的方式是,每次我单击某处时,终端上显示的警告数量越来越高,尤其是当状态更改时。。 >

但是答案是,由于您是在render函数中添加事件侦听器代码,因此每次重新渲染代码时,都会添加越来越多的事件侦听器,从而降低了代码的速度。

基本上,解决方案是将事件侦听器的添加位置移到componentDidMount上,这样它只能运行一次。

更新后的有效javascript:

    const items = [
        {
            value: 'User1'
        },
        {
            value: 'User2'
        },
        {
            value: 'User3'
        },
        {
            value: 'User4'
        },
        {
            value: 'User5'
        }
    ];

class Dropdown extends React.Component {
  state = {
    isActive: false,
  }

  // added component did mount here
  componentDidMount(){
    const { isActive } = this.state;
    document.addEventListener('click', (evt) => {
        if (evt.target.closest('#dropdownContent')) {
          console.warn('clicked inside target do nothing');
          return;
        }

        if (evt.target.closest('#dropdownHeader')) {
          console.warn('clicked the header toggle');
          this.setState({isActive: !isActive});
        }

        console.warn('clicked outside target');
        if (isActive) {
          this.setState({isActive: false});
        }
      });
  }

  render() {
    const { isActive } = this.state;
    //removed event listener here
      return (
          <div id="container">
              <div id="dropdownHeader">select option</div>
              {isActive && (
                <div id="dropdownContent">
                  {items.map((item) => (
                    <div id="item" key={item.value}>
                      {item.value}
                    </div>
                  ))}
                </div>
              )}
          </div>
      );
  };
}
  ReactDOM.render(
    <Dropdown items={items} />,
    document.getElementById('root')
  );