0

我正在尝试使用 useRef 钩子和 Typescript 构建 React Dropdown 组件:如果我单击一次切换按钮或单击它外部,它会正确打开并关闭,但是当我想再次打开它时它会关闭。有任何想法吗 ?这是我以某种方式失去参考吗?

这是用法:

https://codesandbox.io/s/react-typescript-obdgs

import React, { useState, useRef, useEffect } from 'react'
import styled from 'styled-components'

interface Props {}

const DropdownMenu: React.FC<Props> = ({ children }) => {
  const [menuOpen, toggleMenu] = useState<boolean>(false)
  const menuContent = useRef<HTMLDivElement>(null)

  useEffect(() => {
    // console.log(menuOpen)
  }, [menuOpen])

  const showMenu = (event: React.MouseEvent) => {
    event.preventDefault()
    toggleMenu(true)
    document.addEventListener('click', closeMenu)
  }

  const closeMenu = (event: MouseEvent) => {
    const el = event.target
    if (menuContent.current) {
      if (el instanceof Node && !menuContent.current.contains(el)) {
        toggleMenu(false)
        document.removeEventListener('click', closeMenu)
      }
    }
  }

  return (
    <div>
      <button
        onClick={(event: React.MouseEvent) => {
          showMenu(event)
        }}
      >
        Open
      </button>
      {menuOpen ? <div ref={menuContent}>{children}</div> : null}
    </div>
  )
}

export default DropdownMenu
4

2 回答 2

1

如果您单击该按钮两次,您将无法再次打开它。如果您在按钮外部单击以关闭,它将按预期工作。

这可能是因为即使菜单已经显示,您的 showMenu 回调也会执行,这会导致附加多个 closeMenu 事件侦听器,从而导致奇怪的行为。

closeMenu 事件侦听器应该在效果内创建,而不是在 showMenu 回调中。

const showMenu = (event: React.MouseEvent) => {
    event.preventDefault()
    toggleMenu(true)
}

// closeMenu is the same
const closeMenu = (event: MouseEvent) => {
    const el = event.target
    if (menuContent.current) {
        if (el instanceof Node && !menuContent.current.contains(el)) {
            toggleMenu(false)
            document.removeEventListener('click', closeMenu)
        }
    }
}

useEffect(() => {
    if (!menuOpen) {
        return
    }
    document.addEventListener('click', closeMenu)
    return () => {
        document.removeEventListener('click', closeMenu)
    }
}, [menuOpen])

useEffect 真的很酷 - 当 menuOpen 被改变和组件被卸载时,移除事件监听器的返回函数将被调用。在您之前的代码中,如果要卸载组件,则不会删除事件侦听器。

于 2020-04-15T10:47:43.340 回答
0

问题出在您的 onClick 按钮上。每次单击按钮时,您都在调用,showMenu因此每次都在添加新的事件侦听器。

showMenu如果菜单已经显示,您不想打电话,因此可以修复:

<button onClick={(event: React.MouseEvent) => {
  if (!menuOpen) showMenu(event);
}}>
于 2020-04-15T10:44:18.047 回答