React Hooks: повторная визуализация, даже если состояние не изменилось - PullRequest
0 голосов
/ 30 сентября 2019

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

Я пришел к следующему решению и подумал, что повторное рендеринг компонента Header будетпроисходит только в том случае, если значение headerState действительно изменилось, а это не то, что происходит. Компонент будет перерисовываться при каждом изменении позиции прокрутки, даже когда setHeaderState даже не вызывается.

Что мне здесь не хватает? Это рендеринг, потому что я использую useWindowScroll и могу ли я как-то избежать этого?

 import { useWindowScroll } from "react-use";

const useHeaderState = () => {
  const [ headerState, setHeaderState ] = React.useState(0);

  const { y } = useWindowScroll();

  React.useEffect(() => {
    if (y > 50 && headerState !== 1) {
      setHeaderState(1);
    }
  }, [y, headerState]);

  return headerState;
}

const Header: React.FC = () => {
  const headerState = useHeaderState();

  console.log("rerender");

  return (
    <header
      className={cn(
        "fixed top-0 left-0 w-full text-white z-30 transition-transform transition-250",
        headerState === 1 && "-translate-16",
      )}
    >
      <div className="bg-gray-900 h-16 md:h-32">header</div>
    </header>
  )
};

Ответы [ 2 ]

0 голосов
/ 01 октября 2019

Повторный рендеринг действительно из-за useWindowScroll. Как видно из source , useWindowScroll вызывает setState при каждой прокрутке окна.

Чтобы избежать ненужного рендеринга, вы можете прослушивать прокрутку окна самостоятельно вместо использования useWindowScroll, например:

const useHeaderState = () => {
  const [ headerState, setHeaderState ] = React.useState(0);

  const frame = useRef(0);
  useEffect(() => {
    const handler = () => {
      cancelAnimationFrame(frame.current);
      frame.current = requestAnimationFrame(() => {
        if (window.pageYOffset > 50 && headerState !== 1) {
          setHeaderState(1);
        }
    };

    window.addEventListener('scroll', handler, {
      capture: false,
      passive: true,
    });

    return () => {
      cancelAnimationFrame(frame.current);
      window.removeEventListener('scroll', handler);
    };
  }, []);

  return headerState;
}
0 голосов
/ 30 сентября 2019

Проблема, как вы сказали, заключается в том, что useWindowScroll внутренне, вероятно, вызывает setState на своей собственной useState ловушке.

Как правило, это не должно быть проблемой, которую компонент повторно отображает? Есть ли проблема с этим?

Однако можно переместить хук useHeaderState к родителю и затем использовать React.memo, чтобы запомнить Header и только повторносделать, если реквизиты изменились.

import * as React from "react";
import { render } from "react-dom";

import { useWindowScroll } from "react-use";

const useHeaderState = () => {
  const [headerState, setHeaderState] = React.useState(0);

  const { y } = useWindowScroll();

  React.useEffect(() => {
    if (y > 50 && headerState !== 1) {
      setHeaderState(1);
    }
  }, [y, headerState]);

  return headerState;
};

const Header: React.FC<{ headerState: number }> = React.memo(props => {
  console.log("rerender");

  return (
    <header className={props.headerState === 1 && "class"}>
      <div className="bg-gray-900 h-16 md:h-32">header</div>
    </header>
  );
});

function App() {
  const headerState = useHeaderState();

  return (
    <div style={{ height: 2000 }}>
      <Header headerState={headerState} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...