React如何使用useEffect处理自定义钩子中的回调

问题描述 投票:0回答:1

我正在尝试编写一个简单的

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
的需要?

reactjs hook
1个回答
0
投票

不确定,但您可以使用 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}
    </>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.