我们经常封装一些自己的下拉列表组件 或者 弹窗组件。一般 点击按钮显示 列表或 弹窗。再次点击 隐藏或关闭,但 ui库里的下拉列表,点击除了自己本身也能实现隐藏对应的列表。下面我就给大家一个实现思路。
弹窗组件可以用 React Portals 实现:react 官网 createPortal
这是一个 简单的点击按钮,显示列表。再次点击隐藏。但我的需求是,点击其他地方也隐藏。
import React, { ReactNode, useEffect, useState, useRef } from "react";
const MyList = () => {
const [visable, setVisabled] = useState(false);
return (
<div>
<button onClick={() => setVisabled(!visable)}>点击显示 列表</button>
<div style={{ display: visable ? "block" : "none" }}>
<div>11111</div>
<div>22222</div>
</div>
</div>
);
};
export default MyList;
可以利用contains 结合 ref(建议用ref,当然也可以用 document方法,给元素加个 id) 。
contains 是 DOM 元素的方法,用于确定一个元素是否包含另一个元素。不包含则执行 关闭/隐藏事件。
还需要 注意的是,需要 给 隐藏、显示的点击事件加个 阻止事件冒泡的方法event.stopPropagation();
阻止事件冒泡方法必须加上!!!
代码如下:
import React, { ReactNode, useEffect, useState, useRef } from "react";
const MyList = () => {
const [visable, setVisabled] = useState(false);
// 下拉列表 ref
const dropdownRef = useRef(null);
useEffect(() => {
//实现点击 本元素外的元素时,隐藏下拉列表(点击其他地方隐藏下拉列表)
function handleOutsideClick(event) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setVisabled(false);
}
}
document.addEventListener("click", handleOutsideClick);
return () => {
document.removeEventListener("click", handleOutsideClick);
};
}, []);
const hanldClick = (event) => {
event.stopPropagation(); // 阻止事件冒泡
setVisabled(!visable);
};
return (
<div>
<button onClick={hanldClick}>点击显示 列表</button>
<div ref={dropdownRef} style={{ display: visable ? "block" : "none" }}>
<div>11111</div>
<div>22222</div>
</div>
</div>
);
};
export default MyList;
核心代码就是这块:
需要注意的是 绑定的事件需要 在 组件卸载时移除。
useEffect(() => {
//实现点击 本元素外的元素时,隐藏下拉列表(点击其他地方隐藏下拉列表)
function handleOutsideClick(event) {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
setVisabled(false);
}
}
document.addEventListener("click", handleOutsideClick);
return () => {
document.removeEventListener("click", handleOutsideClick);
};
}, []);
类组件就是这样:
只是把useEffetc里的内容 转换成Class语法。componentDidMount 绑定事件,componentWillUnmount移除事件。
import React, { Component } from "react";
class MyList extends Component {
constructor(props) {
super(props);
this.state = {
visible: false
};
this.dropdownRef = React.createRef(); // 下拉列表 ref
}
componentDidMount() {
// 实现点击本元素外的元素时,隐藏下拉列表(点击其他地方隐藏下拉列表)
document.addEventListener("click", this.handleOutsideClick);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleOutsideClick);
}
handleOutsideClick = (event) => {
if (
this.dropdownRef.current &&
!this.dropdownRef.current.contains(event.target)
) {
this.setState({ visible: false });
}
};
handleClick = (event) => {
event.stopPropagation(); // 阻止事件冒泡
this.setState((prevState) => ({
visible: !prevState.visible
}));
};
render() {
const { visible } = this.state;
return (
<div>
<button onClick={this.handleClick}>点击显示列表</button>
<div
ref={this.dropdownRef}
style={{ display: visible ? "block" : "none" }}
>
<div>11111</div>
<div>22222</div>
</div>
</div>
);
}
}
export default MyList;
理论上来说 这个思路在 vue里也适用,只需要转化vue语法。不过vue 有指令库 v-clickoutside。
题外话:现在ai真的很强大,我类组件的写法就是让ai转换的,竟然完全正确,运行无误!!! 如果简单的组件 可以试试 ai 。比如 chatGPT\文心一言\通义千问。
比如:文心给的 就可以用,编程方面 chatGPT会比文心强,目前 我认为 gpt是独一档的强!
文心一言 提问分享