我正在尝试编写一个简单的
useClickOutside
自定义钩子,给定一个回调和一个元素,每当用户在该元素之外单击时调用回调。
它看起来像这样:
function useClickOutside(handler) {
const ref = useRef();
useEffect(() => {
const handleClick = (e) => {
if(ref.current && !ref.current.contains(e.target)) {
handler();
}
};
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, [handler]);
return ref;
}
并在组件中使用它(示例):
function Example(props) {
const menuRef = useClickOutside(() => {
alert('You just clicked outside the menu.');
});
// some other component logic
return (
<div>
<Menu ref={menuRef} />
</div>
);
}
这工作正常,但有一个问题:每次重新渲染示例组件时,
useEffect
内的useClickOutside
都会再次运行,这是不必要的。这是因为 handler
作为 useEffect
的依赖项传递(正如 linter 建议的那样),并且每次示例重新渲染时,由于引用相等的工作方式,回调被视为“新函数”。
幸运的是,React对此有一个解决方案,您所要做的就是将处理程序包装在
useCallback
中。
const menuRef = useClickOutside(useCallback(() => {
alert('You just clicked outside the menu.');
}, []));
现在,
useEffect
内的useClickOutside
仅在这种特殊情况下安装示例组件时运行。
但是,对我来说,这似乎确实是反模式。没有什么可以阻止我们在没有
useCallback
的情况下传递常规函数,这将导致对 useEffect
的不必要的调用。
有没有更好、更优雅的方式来编写这个
useClickOutside
钩子,从而可能消除对 useCallback
的需要?
不确定,但您可以使用 useCallback 挂钩来记住 loadTab 函数,以便它在渲染之间保持相同。这可确保将相同的函数引用传递给每个 SwitchTabButton 实例,并且状态更改按预期工作。试试这个代码:-
import { useState, useCallback } from "react";
export default function Tabs() {
console.log("create tabs");
const SwitchTabButton = buttonCreator();
const [tab, setTab] = useState(null);
const loadTab = useCallback(
(content) => {
setTab(content);
},
[setTab]
);
const tabsData = [
{
name: "Button 1",
content: "Tab 1",
},
{
name: "Button 2",
content: "Tab 2",
},
{
name: "Button 3",
content: "Tab 3",
},
];
return (
<>
{tabsData.map((data, index) => (
<SwitchTabButton
key={index}
loadTab={loadTab}
tabName={data.name}
tabContent={data.content}
/>
))}
{tab}
</>
);
}