React 下拉菜单只切换一次
React dropdown toggles only once
我正在尝试使用 useRef 钩子和 Typescript 构建 React Dropdown 组件:
如果我单击一次切换按钮或在其外部单击,它会正确打开并关闭,但是当我想再次打开它时它会关闭。有任何想法吗 ?这是我以某种方式丢失了 ref 引用吗?
用法如下:
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
问题出在您的 onClick 按钮上。每次单击按钮时,您都在调用 showMenu
,因此您每次都在添加新的事件侦听器。
如果菜单已经显示,您不想调用 showMenu
,因此修复可以是:
<button onClick={(event: React.MouseEvent) => {
if (!menuOpen) showMenu(event);
}}>
如果您点击该按钮两次,您将无法再次打开它。如果您在按钮外部单击以关闭,它将按预期工作。
这可能是因为即使菜单已经显示,您的 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 时和卸载组件时调用。在您之前的代码中,如果卸载组件,则不会删除事件侦听器。
我正在尝试使用 useRef 钩子和 Typescript 构建 React Dropdown 组件: 如果我单击一次切换按钮或在其外部单击,它会正确打开并关闭,但是当我想再次打开它时它会关闭。有任何想法吗 ?这是我以某种方式丢失了 ref 引用吗?
用法如下:
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
问题出在您的 onClick 按钮上。每次单击按钮时,您都在调用 showMenu
,因此您每次都在添加新的事件侦听器。
如果菜单已经显示,您不想调用 showMenu
,因此修复可以是:
<button onClick={(event: React.MouseEvent) => {
if (!menuOpen) showMenu(event);
}}>
如果您点击该按钮两次,您将无法再次打开它。如果您在按钮外部单击以关闭,它将按预期工作。
这可能是因为即使菜单已经显示,您的 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 时和卸载组件时调用。在您之前的代码中,如果卸载组件,则不会删除事件侦听器。