如何使 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 对象上的事件侦听器,这会导致错误。
我想使用自定义挂钩切换输入元素。
这是我的自定义挂钩:
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 对象上的事件侦听器,这会导致错误。