如何使反应钩子与 ref 保持一致

问题描述

我想使用自定义钩子切换输入元素。

这是我的自定义钩子:

import { RefObject,useEffect } from "react";

export const useEscape = (
  ref: RefObject<HTMLElement>,triggerFn: () => void
) => {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        triggerFn();
      }
    };
    document.addEventListener("click",handleClickOutside);

    return () => window.removeEventListener("click",handleClickOutside);
  });
};

以及使用钩子的示例

import * as React from "react";
import "./styles.css";
import { useEscape } from "./useEscape";

export default function App() {
  const [showInput,setShowInput] = React.useState(false);
  const inputRef = React.useRef(null);
  useEscape(inputRef,() => {
    if (showInput) setShowInput(false);
  });

  return (
    <div>
      {showInput && (
        <input ref={inputRef} placeholder="click outside to toggle" />
      )}

      {!showInput && (
        <span
          style={{ border: "1px solid black" }}
          onClick={() => {
            console.log("toggle to trigger");
            setShowInput(true);
          }}
        >
          click to toggle input
        </span>
      )}
    </div>
  );
}

这是代码和盒子演示的 link

问题来了。在我点击 span 元素切换到输入状态后。在 input 元素外单击后,它将永远无法再次切换回 input 状态。

我想我知道为什么会这样。 react ref 仍然指向最初创建的 input 元素。然而,当反应切换到显示 span 状态时,它会卸载 input 元素,并且我的自定义钩子永远不会与新的 input 元素的 React 同步。如何自定义我的 useEscape 钩子,以便 react ref 同步? (顺便说一下,我不想使用样式作为在视觉上“隐藏”输入元素的解决方法)。

解决方法

import { RefObject,useEffect } from "react";

export const useEscape = (
  ref: RefObject<HTMLElement>,triggerFn: () => void
) => {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        triggerFn();
      }
    };
    document.addEventListener("click",handleClickOutside);

    return () => document.removeEventListener("click",handleClickOutside);
  },[ref,triggerFn]);
};

你的整个逻辑是绝对正确的。有一个轻微的错误,而不是 window.removeEventListener,改成document.removeEventListener。

您正在删除导致错误的全局窗口对象上的事件侦听器。