如何避免在我的组件中使用反应挂钩进行额外渲染

How to avoid extra renders in my component to use react hooks

我尝试使用反应钩子而不是基于 class 的组件,但在性能方面存在一些问题。

代码:

import React, { memo, useCallback, useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

let counter = -1;

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = useCallback(() => setToggleValue(!toggleValue), [
    toggleValue,
    setToggleValue
  ]);
  return [toggleValue, toggler];
}

const Header = memo(({ onClick }) => {
  counter = counter + 1;
  return (
    <div>
      <h1>HEADER</h1>
      <button onClick={onClick}>Toggle Menu</button>
      <div>Extra Render: {counter}</div>
    </div>
  );
});

const Dashboard = memo(() => {
  const [visible, toggle] = useToggle(false);
  const handleMenu = useCallback(
    () => {
      toggle(!visible);
    },
    [toggle, visible]
  );

  return (
    <>
      <Header onClick={handleMenu} />
      <div>Dashboard with hooks</div>
      {visible && <div>Menu</div>}
    </>
  );
});

export default Dashboard;

这是我想做的一个例子:Example

如您所见,我的 Header 组件中有额外的渲染。 我的问题:是否有可能避免额外渲染以使用 react-hooks?

如果您使用回调模式来更新状态,您将能够避免额外的重新渲染,因为该函数不需要一次又一次地创建,并且您只需在第一次渲染时使用 create handleMenu

const Dashboard = memo(() => {
  const [visible, toggle] = useToggle(false);
  const handleMenu = useCallback(() => {
    toggle(visible => !visible);
  }, []);

  return (
    <>
      <Header onClick={handleMenu} />
      <div>Dashboard with hooks</div>
      {visible && <div>Menu</div>}
    </>
  );
});

Working Demo

更改您的自定义挂钩 useToggle 以使用功能状态 setter,像这样

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = useCallback(() => setToggleValue(toggleValue => !toggleValue));
  return [toggleValue, toggler];
}

并像这样使用它:

const Dashboard = memo(() => {
  const [visible, toggle] = useToggle(false);
  const handleMenu = useCallback(
    () => {
      toggle();
    }, []
  );

  return (
    <>
      <Header onClick={handleMenu} />
      <div>Dashboard with hooks</div>
      {visible && <div>Menu</div>}
    </>
  );
});

完整示例:https://codesandbox.io/s/z251qjvpw4

编辑

这可以更简单(感谢@DoXicK)

function useToggle(initialValue) {
  const [toggleValue, setToggleValue] = useState(initialValue);
  const toggler = useCallback(() => setToggleValue(toggleValue => !toggleValue), [setToggleValue]);
  return [toggleValue, toggler];
}


const Dashboard = memo(() => {
  const [visible, toggle] = useToggle(false);

  return (
    <>
      <Header onClick={toggle} />
      <div>Dashboard with hooks</div>
      {visible && <div>Menu</div>}
    </>
  );
});

这是 useCallback 过于频繁地失效的问题。 (这里有关于 React 回购的讨论:https://github.com/facebook/react/issues/14099

因为每次 toggle 值更改和 return 新函数时 useCallback 都会失效,然后将新的 handleMenu 函数传递给 <Header /> 原因它重新渲染。

解决方法是创建自定义 useCallback 挂钩:

(复制自 https://github.com/facebook/react/issues/14099#issuecomment-457885333

function useEventCallback(fn) {
  let ref = useRef();
  useLayoutEffect(() => {
    ref.current = fn;
  });
  return useMemo(() => (...args) => (0, ref.current)(...args), []);
}

示例:https://codesandbox.io/s/1o87xrnj37