为什么我们要在没有依赖数组的情况下使用 useEffect?

IT技术 javascript reactjs react-hooks
2021-03-24 19:57:23

我有这个代码:

const App: React.FC = () => {
  const [isOpen, setIsOpen] = React.useState(true);
  const [maxHeight, setMaxHeight] = React.useState();

  const wrapper = React.useRef<HTMLDivElement>(null);
  const content = React.useRef<HTMLDivElement>(null);

  const setElementMaxHeight = () => {
    if (content && content.current) {
      setMaxHeight(isOpen ? content.current.offsetHeight : 0);
    }
  };

  useEffect(() => {
   setElementMaxHeight();

    window.addEventListener("resize", setElementMaxHeight);

    return () => {
      window.removeEventListener("resize", setElementMaxHeight);
    };
  });

  const toggle = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div>
      <button onClick={toggle}>
        <span className="nominal-result__expander fa" />
      </button>
      <div
        className="nominal-results__list-wrapper"
        ref={wrapper}
        style={!!maxHeight ? { maxHeight: `${maxHeight}px` } : undefined }
      >
        <div className="nominal-results__list" ref={content} />
      </div>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

这将在每个渲染上添加和删除一个事件处理程序。

这一定是坏事吗,这真的从成为一个钩子中获得了什么吗?

这是在代码审查中提出的,我说它很糟糕,因为它在每次渲染时添加和删除了事件侦听器。

1个回答

对于这种确切的情况,您是对的,因为undefined作为useEffect.

这意味着useEffect在每个渲染上运行,因此事件处理程序将不必要地分离并重新附加到每个渲染上。

function listener() {
  console.log('click');
}

function Example() {
  const [count, setCount] = window.React.useState(0);

  window.React.useEffect(() => {

    console.log(`adding listener ${count}`);
    window.addEventListener("click", listener);

    return () => {
      console.log(`removing listener ${count}`);
      window.removeEventListener("click", listener);
    };
  }); // <-- because we're not passing anything here, we have an effect on each render
  
  window.React.useEffect(() => {
    setTimeout(() => {
      setCount(count + 1);
    }, 1000)
  });
  
  return count;
}

window.ReactDOM.render(window.React.createElement(Example), document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

但是,如果您通过传入一个空数组来明确声明没有依赖关系[]useEffect则只会运行一次,从而使这种模式对于事件处理程序附件来说是完全合法的。

function listener() {
  console.log('click');
}

function Example() {
  const [count, setCount] = window.React.useState(0);

  window.React.useEffect(() => {

    console.log(`adding listener ${count}`);
    window.addEventListener("click", listener);

    return () => {
      console.log(`removing listener ${count}`);
      window.removeEventListener("click", listener);
    };
  }, []); // <-- we can control for this effect to run only once during the lifetime of this component
  
  window.React.useEffect(() => {
    setTimeout(() => {
      setCount(count + 1);
    }, 1000)
  });
  
  return count;
}

window.ReactDOM.render(window.React.createElement(Example), document.getElementById('root'))
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>

“..事件处理程序将在每次渲染时不必要地分离和重新附加。”。是不是就好像我们写了一段没有任何钩子的代码一样。例如,假设我们有赋值 (const var = "something"),在每次渲染时,我们都会让赋值工作,就像我们有 useEffect(() => const var = "something") 一样。请不要考虑变量作用域
2021-05-27 19:57:23
@nem 在这种情况下,您不能在不使用 useEffect 的情况下仅在函数中声明该功能吗?
2021-05-30 19:57:23
这取决于用例。渲染周期内的同步函数调用将在渲染内运行,它可能会影响性能。效果总是在渲染之后运行,因此至少对 UX 的损害较小,因为在效果开始运行时屏幕已经显示了它应该显示的内容。
2021-06-03 19:57:23
那么为什么你会在没有依赖数组的情况下使用 useEffect 呢?
2021-06-12 19:57:23
@dwjohnston 好吧,您可以将它用于需要在每次渲染上运行的任何操作。这是什么操作取决于您正在构建的应用程序。
2021-06-13 19:57:23