如何避免在我的组件中使用多余的渲染反应钩子

时间:2019-02-14 09:56:37

标签: reactjs react-hooks

我尝试使用react挂钩而不是基于类的组件,并且在性能方面存在一些问题。

代码:

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的额外渲染?

3 个答案:

答案 0 :(得分:2)

将您的自定义钩子useToggle更改为使用功能状态设置器,例如

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>}
    </>
  );
});

答案 1 :(得分:0)

如果使用回调模式更新状态,则可以避免额外的重新渲染,因为无需一次又一次地创建函数,而只需在第一次渲染时创建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

答案 2 :(得分:0)

这是useCallback经常失效的问题。 (在此处的{@ {3}}的React repo上有关于此的对话)

因为每次useCallback值更改时toggle将失效并返回一个新函数,然后将一个新的handleMenu函数传递给<Header />导致它重新呈现。 / p>

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

(从https://github.com/facebook/react/issues/14099复制)

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

示例:https://github.com/facebook/react/issues/14099#issuecomment-457885333