javascript - 解开 onMouseEnter、onMouseLeave 和 onClick 中的状态变化
问题描述
我有以下组件
const ListItem = ({ children, title = '' }) => {
const [isActive, setIsActive] = useState(false)
useEffect(() => {
console.log('isActive', isActive)
}, [isActive])
return (
<li className={`depth1${(isActive ? ' is-active' : '')}`} onMouseEnter={() => {
console.log('onMouseEnter')
setIsActive(true)
}} onMouseLeave={() => {
console.log('onMouseLeave')
setIsActive(false)
}}>
<a href="#" onClick={(e) => {
console.log('onClick a')
e.preventDefault()
setIsActive(!isActive)
}}>{title}</a>
{isActive && (
<ul onClick={() => {
console.log('onClick ul')
setIsActive(!isActive)
}}>
{children}
</ul>
)}
</li>
)
}
该组件在以下上下文中使用
<ul>
<ListItem title="BRAND">
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
</ListItem>
<ListItem title="SERVICE">
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
</ListItem>
<ListItem title="CLIENT">
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
</ListItem>
<ListItem title="CONTENTS">
<li>
<a href="#" onClick={(e) => {
e.preventDefault()
}}>Link</a>
</li>
</ListItem>
</ul>
在我的电脑上,当鼠标悬停在ListItem
、onMouseEnter
激活和控制台日志上时
onMouseEnter
isActive true
在我的电脑上,当鼠标离开时ListItem
,onMouseLeave
激活和控制台日志
onMouseLeave
isActive false
这里的一切都很好。当我打开导航菜单时,问题出在移动设备上,我控制台日志
isActive false
正如预期的那样。
但是当我ListItem
第一次点击 a 时,我控制台日志
onMouseEnter
isActive true
onClick a
isActive false
而我的ListItem
children
不打开。但是当我ListItem
第二次点击控制台日志时
onClick a
isActive true
这会打开我的ListItem
children
. 如果我ListItem
第三次点击,我控制台日志
onClick a
isActive false
这关闭了我的ListItem
children
如何解开我的状态变化,以便在我的第一次而不是第二次点击时,我ListItem
children
在移动设备上打开?
解决方案
这是一个令人惊讶的棘手问题。您可以尝试几种方法。我没有时间亲自尝试它们。
- 方法 1:您可以在元素上使用本机非冒泡事件(mouseenter、mouseleave)
<li>
。一个更方便地使用本机事件的钩子是useEvent(只需复制代码,因为我还没有决定那个库的未来)
import React, { useState, useCallback } from 'react';
import classnames from 'classnames';
import { useEvent } from 'the/path/to/the/hook';
const useMouseEnter = (listener, options) => useEvent('mouseenter', listener, options);
const useMouseLeave = (listener, options) => useEvent('mouseleave', listener, options);
const ListItem = ({ children, title = '' }) => {
const [isActive, setIsActive] = useState(false);
const toggle = () => setIsActive(_ => !_);
const open = () => setIsActive(true);
const close = () => setIsActive(false);
const [setMouseEnterTarget] = useMouseEnter(open); // the setters are stable...
const [setMouseLeaveTarget] = useMouseLeave(close);
const setRef = useCallback((element) => {
setMouseEnterTarget(element);
setMouseLeaveTarget(element);
}, []); // ...so we don't need them in the dependencies array
return (
<li ref={setRef} className={classnames('depth1', { 'is-active': isActive })}>
<a href="#" onClick={(e) => {
console.log('onClick a');
e.preventDefault();
toggle();
}}>{title}</a>
- 方法 2:您可以检测是否
hover
是您当前环境的一个特性,并相应地控制您的代码行为:
const [isActive, setIsActive] = useState(false);
const toggle = () => setIsActive(_ => !_);
const open = () => setIsActive(true);
const close = () => setIsActive(false);
const supportsHover = useMedia('(hover: hover)');
const mouseEvents = {
onMouseEnter: open,
onMouseLeave: close,
};
return (
<li className={classnames('depth1', { 'is-active': isActive })}
{ ...supportsHover ? mouseEvents : undefined }
>
<a href="#" onClick={(e) => {
console.log('onClick a');
e.preventDefault();
toggle();
}}>{title}</a>
和钩子
import { useState, useEffect } from 'react';
export const useMedia = (query) => {
const [matches, setMatches] = useState(window.matchMedia(query).matches);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) setMatches(media.matches);
const listener = () => setMatches(media.matches);
media.addListener(listener);
return () => media.removeListener(listener);
}, [matches, query]);
return matches;
};
方法 3:您可以在每次切换后启动例如 250 毫秒的计时器,并忽略进一步的切换,直到计时器到期
方法4:您可以通过同步设置标志和异步删除标志(因此在下一个事件循环迭代中)来防止每个事件循环多次切换状态(但我不知道是否触发了有问题的事件在移动设备上同步)
推荐阅读
- python - Django添加材料
- html - 使用webbrowser控件VB.NET通过它的宽度获取网站表的价值
- python - 从excel python获取空单元格的无值
- c# - 如何将字符串的数组或列表集合转换为字典,这些字符串是一组由字符分隔或分隔的键和值对?
- html - 为什么汉堡菜单会脱离包装块?
- android - Android 资源链接失败(构建 gradle 错误)
- flutter - 无法获取 WebSocketChannel 的重试逻辑
- javascript - 如何将sharedarraybuffer渲染到js画布
- c# - 如何在不保留对原始字典的引用的情况下获取字典的 KeyValuePair 的副本?可克隆KVP?
- javascript - 检查空输入Javascript