Как избежать лишних рендеров в моем компоненте, чтобы использовать реакционные хуки - PullRequest
0 голосов
/ 14 февраля 2019

Я пытаюсь использовать реагирующие хуки вместо компонентов на основе классов, и у меня возникают проблемы с производительностью.

Код:

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;

Вот пример того, что я хочу сделать: Пример .

Как видите, есть дополнительные рендеры вмой заголовочный компонент.Мой вопрос: можно ли избежать лишних рендеров для использования реакционных хуков?

Ответы [ 3 ]

0 голосов
/ 14 февраля 2019

Измените свой пользовательский хук 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>}
    </>
  );
});
0 голосов
/ 14 февраля 2019

Это проблема с useCallback слишком часто становятся недействительными.(об этом говорится в репозитории React: https://github.com/facebook/react/issues/14099)

, поскольку useCallback будет аннулироваться каждый раз при изменении значения toggle и возвращать новую функцию, а затем передавать новую функцию 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

0 голосов
/ 14 февраля 2019

Если вы используете шаблон обратного вызова для обновления состояния, вы сможете избежать дополнительных повторных визуализаций, поскольку функцию не нужно создавать снова и снова, и вы просто используете 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>}
    </>
  );
});

Рабочая демоверсия

...