如何在 React 中创建右键菜单
介绍
React 是一个出色的库,用于构建基于组件的 UI,并考虑到可重用性。您可以创建大量组件并在多个页面上重复使用它们。有时,出于样式目的,您可能希望覆盖 React 应用中的默认右键菜单或上下文菜单。
您可以通过创建自定义组件来处理右键单击并显示菜单,从而轻松创建上下文菜单。在本指南中,您将学习如何在 React 应用中创建可重复使用且灵活的上下文菜单组件。
实施概述
要覆盖默认的浏览器右键菜单,第一步是阻止默认的右键行为。这可以通过监听contextmenu事件并在事件上使用PreventDefault()方法来实现。
document.addEventListener("contextmenu", (event) => {
event.preventDefault();
});
接下来,捕获当前 (x,y) 位置,并在页面上的该坐标处呈现组件。从事件对象的pageX和pageY属性获取 x 和 y 位置。
document.addEventListener("contextmenu", (event) => {
event.preventDefault();
const xPos = event.pageX + "px";
const yPos = event.pageY + "px";
//
});
然后,将坐标作为位置样式属性传递给组件。这里要注意的关键点是,自定义菜单的位置样式应设置为绝对值,以便菜单在正确的位置打开。
<ContextMenu style={{ top: xPos, left: yPos }} />
这是实施的总体概述。
首先定义组件和组件的状态。
class ContextMenu extends Component {
state = {
xPos: "0px",
yPos: "0px:,
showMenu: false
}
render() {
// ...
}
}
状态将包含(x,y)位置和用于显示或隐藏菜单的布尔状态。
接下来,在组件中的componentDidMount生命周期方法中添加事件监听器。
class ContextMenu extends Component {
state = {
xPos: "0px",
yPos: "0px:,
showMenu: false
}
componentDidMount() {
document.addEventListener("click", this.handleClick);
document.addEventListener("contextmenu", this.handleContextMenu);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleClick);
document.removeEventListener("contextmenu", this.handleContextMenu);
}
handleClick = (e) => {
// ...
}
handleContextMenu = (e) => {
e.preventDefault();
// ...
}
render() {
// ...
}
}
在componentDidMount方法中为单击和上下文菜单事件添加两个事件监听器。不要忘记在componentWillUnmount方法中删除事件监听器,以避免组件出现内存泄漏。
在handleClick方法中,如果菜单当前在页面中打开,则需要关闭该菜单。
handleClick = (e) => {
if (this.state.showMenu) this.setState({ showMenu: false });
};
在handleContextMenu方法中,你要做的就是防止默认事件触发,设置状态中右键单击的当前(x,y)位置,并显示菜单。
handleContextMenu = (e) => {
e.preventDefault();
this.setState({
xPos: `${e.pageX}px`,
yPos: `${e.pageY}px`,
showMenu: true,
});
};
在render方法中,创建一个包含菜单项的列表,并在showMenu状态设置为 true 时显示它。
class ContextMenu extends Component {
// ...
render() {
const { showMenu, xPos, yPos } = this.state;
if (showMenu)
return (
<ul
className="menu"
style={{
top: yPos,
left: xPos,
}}
>
<li>Login</li>
<li>Register</li>
<li>Open Profile</li>
</ul>
);
else return null;
}
}
如果您现在查看结果,则在右键单击页面时应该能够看到自定义菜单。您获得了所需的结果,但此组件仍需要一些重构。菜单组件在此处是硬编码的;最好将其作为 prop 出现。这样,您可以在不同的页面上显示不同的上下文菜单。此外,使用react-motion库在菜单打开时添加一些淡入淡出动画。
class ContextMenu extends Component {
// ...
render() {
const { showMenu, yPos, xPos } = this.state;
return (
<Motion
defaultStyle={{ opacity: 0 }}
style={{ opacity: !showMenu ? spring(0) : spring(1) }}
>
{(interpolatedStyle) => (
<>
{showMenu ? (
<div
className="menu-container"
style={{
top: yPos,
left: xPos,
opacity: interpolatedStyle.opacity,
}}
>
{this.props.menu}
</div>
) : (
<></>
)}
</>
)}
</Motion>
);
}
}
现在您可以从根组件或入口组件中使用ContextMenu组件,如下所示。
const CustomMenu = () => (
<ul className="menu">
<li>Login</li>
<li>Register</li>
<li>Open Profile</li>
</ul>
);
const App = () => (
<div className="App">
{/* ... */}
<ContextMenu menu={() => <CustomMenu>}>
</div>
)
使用钩子创建自定义上下文菜单
上一节概述了如何创建组件来呈现自定义上下文菜单。但那是一个类组件;您可以使用钩子对函数组件执行相同的操作。
所有状态变量都可以通过useState钩子创建和管理。使用useCallback钩子处理回调并将依赖项数组传递给它,以便它可以记住结果,并且仅在依赖项更新时更改。将所有逻辑包装在单独的自定义钩子中,并从钩子返回 xPos 、 yPos和showMenu状态变量。
const useContextMenu = () => {
const [xPos, setXPos] = useState("0px");
const [yPos, setYPos] = useState("0px");
const [showMenu, setShowMenu] = useState(false);
const handleContextMenu = useCallback(
(e) => {
e.preventDefault();
setXPos(`${e.pageX}px`);
setYPos(`${e.pageY}px`);
setShowMenu(true);
},
[setXPos, setYPos]
);
const handleClick = useCallback(() => {
showMenu && setShowMenu(false);
}, [showMenu]);
useEffect(() => {
document.addEventListener("click", handleClick);
document.addEventListener("contextmenu", handleContextMenu);
return () => {
document.addEventListener("click", handleClick);
document.removeEventListener("contextmenu", handleContextMenu);
};
});
return { xPos, yPos, showMenu };
};
const ContextMenu = ({ menu }) => {
const { xPos, yPos, showMenu } = useContextMenu();
return (
<Motion
defaultStyle={{ opacity: 0 }}
style={{ opacity: !showMenu ? spring(0) : spring(1) }}
>
{(interpolatedStyle) => (
<>
{showMenu ? (
<div
className="menu-container"
style={{
top: yPos,
left: xPos,
opacity: interpolatedStyle.opacity,
}}
>
{menu}
</div>
) : (
<></>
)}
</>
)}
</Motion>
);
};
结论
可以将本指南视为探索更高级用例的门户,例如在功能组件中创建自定义钩子、覆盖默认浏览器 UI 组件(如上下文菜单)等。如果您了解自定义 UI 组件背后的实现逻辑,您将能够更好地开发自定义 UI 组件。无论如何,要覆盖默认行为,您需要做的第一件事是使用PreventDefault()来阻止事件。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~