【问题标题】:React Hooks: Rerender even though state didn't changeReact Hooks:即使状态没有改变也要重新渲染
【发布时间】:2019-09-30 20:33:49
【问题描述】:

我正在尝试实现一个简单的 Header 组件,该组件在到达某个滚动位置后会隐藏标题。

我想出了以下解决方案,并认为只有在 headerState 的值实际发生变化时才会重新渲染 Header 组件,但事实并非如此。该组件将在每次滚动位置更改时重新呈现,即使 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>
  )
};

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    重新渲染确实是因为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;
    };
    

    【讨论】:

      【解决方案2】:

      像你说的问题是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);
      

      【讨论】:

      • 这将使我的 App 组件在每次滚动位置更改时重新呈现,不是吗?一般来说,我只是尽量避免不必要的重新渲染。一个简单的标头可能不会对性能产生很大的影响,但是随着我的应用程序变得更加复杂,它可能吗?我想在我的例子中,直接使用窗口滚动事件而不是使用另一个钩子可能是最简单的。我只是认为我的 Header 组件仅在 headerState 值更改时重新呈现,但显然这不是它的工作原理
      • 我不会过早优化。我会保持简单。引入复杂性将不可避免地反过来咬你。 React.memo 仅在存在明显性能问题时使用 (reactjs.org/docs/react-api.html#reactmemo)。相反,我不会担心这种不必要的重新渲染,除非您注意到性能问题并使用 React DevTools(或浏览器开发工具)测量它并查明原因并优化该问题。我认为这是对该主题的一个很好的概述:kentcdodds.com/blog/…
      猜你喜欢
      • 2020-11-05
      • 2019-07-22
      • 1970-01-01
      • 2020-06-26
      • 2020-09-24
      • 2020-02-18
      • 2019-09-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多