场景分析
比如说是有个按钮 <button/>
,点击这个按钮会出现下拉框 <Select />
,想点击除了下拉框其他的地方能关闭下拉框。
技术拆解
1. 先写好只能由按钮控制显示隐藏
import React, { useState } from "react";
function Select({ onSelect }) {
const data = ["春", "夏", "秋", "冬"];
return (
<ul
style={{
background: "#fff",
color: "#333",
width: "200px",
border: "1px solid #999",
position: "absolute",
left: "35px",
top: "15px",
}}
>
{data.map((item) => (
<li key={item} onClick={() => onSelect(item)}>
{item}
</li>
))}
</ul>
);
}
function App() {
const [showSelect, setShowSelect] = useState(false);
const [season, setSeason] = useState("");
const clickButton = () => {
setShowSelect(!showSelect);
};
const onSelect = (item) => {
setSeason(item);
};
return (
<div className="App">
<div style={{ position: "relative", width: "300px" }}>
<button onClick={clickButton}>选择现在的季节:</button>
{season}
{showSelect && <Select onSelect={onSelect} />}
</div>
</div>
);
}
export default App;
方法一:通过给 document
增加点击事件监听来做到点其他地方关闭下拉框
这里要特别注意,<button/>
和 <Select />
的区域也是在 document
里的,但是如果点击就隐藏下拉框这不是我们想要的结果,所以还需要在监听方法回调里做判断。
- 使用
useRef
获取到结点数据 。
import React, { useState, useRef } from "react";
// ...
function App() {
// ...
const selectRef = useRef(null);
// ...
return (
<div className="App">
<div style={{ position: "relative", width: "300px" }} ref={selectRef}>
// ...
);
}
- 使用
useEffect
来给document
增加点击事件监听。
import React, { useState, useRef, useEffect } from "react";
//...
function App() {
// ...
useEffect(() => {
if (showSelect) {
document.addEventListener("click", clickCallback, false);
return () => {
document.removeEventListener("click", clickCallback, false);
};
}
}, [showSelect]);
// ...
}
- 编写
clickCallback
回调函数。
import React, { useState, useRef, useEffect } from "react";
//...
function App() {
// ...
function clickCallback(event) {
if (selectRef.current.contains(event.target)) {
return;
}
setShowSelect(false);
}
// ...
}
- 完美,这下点了除
selectRef
以外的地方都会隐藏<Select />
了。
完整代码
import React, { useState, useRef, useEffect } from "react";
function Select({ onSelect }) {
const data = ["春", "夏", "秋", "东"];
return (
<ul
style={{
background: "#fff",
color: "#333",
width: "200px",
border: "1px solid #999",
position: "absolute",
left: "35px",
top: "15px",
}}
>
{data.map((item) => (
<li key={item} onClick={() => onSelect(item)}>
{item}
</li>
))}
</ul>
);
}
function App() {
const [showSelect, setShowSelect] = useState(false);
const [season, setSeason] = useState("");
const selectRef = useRef(null);
const clickButton = () => {
setShowSelect(!showSelect);
};
const onSelect = (item) => {
setSeason(item);
};
function clickCallback(event) {
if (selectRef.current.contains(event.target)) {
return;
}
setShowSelect(false);
}
useEffect(() => {
if (showSelect) {
document.addEventListener("click", clickCallback, false);
return () => {
document.removeEventListener("click", clickCallback, false);
};
}
}, [showSelect]);
return (
<div className="App">
<div style={{ position: "relative", width: "300px" }} ref={selectRef}>
<button onClick={clickButton}>选择现在的季节:</button>
{season}
{showSelect && <Select onSelect={onSelect} />}
</div>
</div>
);
}
export default App;
方法二:通过 input输入框 的 focus 和 blur 事件,点击其他地方会触发 blur
import React, { useState, useRef, useEffect } from "react";
function Select({ onSelect }) {
const data = ["春", "夏", "秋", "冬"];
return (
<ul
style={{
background: "#fff",
color: "#333",
width: "200px",
border: "1px solid #999",
position: "absolute",
left: "35px",
top: "15px",
}}
>
{data.map((item) => (
<li
key={item}
onClick={(e) => {
e.stopPropagation();
onSelect(item, e);
}}
>
{item}
</li>
))}
</ul>
);
}
function App() {
const [showSelect, setShowSelect] = useState(false);
const [season, setSeason] = useState("");
const inputRef = useRef(null);
const clickButton = () => {
inputRef.current.focus();
setShowSelect(true);
};
const onSelect = (item) => {
inputRef.current.focus();
setSeason(item);
inputRef.current.blur();
};
const handleBlur = () => {
// 使用异步延迟执行,不然无法触发 onSelect 事件
setTimeout(() => {
setShowSelect(false);
}, 100);
};
return (
<div className="App">
<div style={{ position: "relative", width: "300px" }}>
<input
ref={inputRef}
onBlur={handleBlur}
style={{
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
opacity: 0,
zIndex: -1,
}}
/>
<button onClick={clickButton}>选择现在的季节:</button>
{season}
{showSelect && <Select onSelect={onSelect} />}
</div>
</div>
);
}
export default App;