如何使 react hook 与 ref 保持一致

How to make react hook persists with 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 到 codesandbox 的演示。

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

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

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。

您正在删除全局 window 对象上的事件侦听器,这会导致错误。