点击 React 组件以外的其他地方执行相应操作的两种方法

场景分析

比如说是有个按钮 <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 里的,但是如果点击就隐藏下拉框这不是我们想要的结果,所以还需要在监听方法回调里做判断。

  1. 使用 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}>
       // ...
  );  
}

  1. 使用 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]);

  // ...
}
  1. 编写 clickCallback 回调函数。
import React, { useState, useRef, useEffect } from "react";

//...
function App() {
  // ...

  function clickCallback(event) {
    if (selectRef.current.contains(event.target)) {
      return;
    }
    setShowSelect(false);
  }

  // ...
}
  1. 完美,这下点了除 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;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容