【问题标题】:Why does this custom hook rendered twice even though the state hasn't changed?为什么即使状态没有改变,这个自定义钩子也会渲染两次?
【发布时间】:2021-08-28 17:35:30
【问题描述】:

我正在创建一个 useInfiniteScroll 只是为了练习制作自定义挂钩。

这就像

import { useEffect, useRef, useState } from "react";

const isHitBottom = () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  return scrollTop + clientHeight >= scrollHeight;
};

export function useInfiniteScroll() {
  console.log("RENDERED");
  const ref = useRef<NodeJS.Timeout | null>(null);
  const [isAtBottom, setIsAtBottom] = useState<boolean>(false);
  const debounced = () => {
    if (ref.current) {
      clearTimeout(ref.current);
    }
    ref.current = setTimeout(() => {
      const isBottom = isHitBottom();
      if (isBottom) {
        console.log("HIT BOTTOM");
        setIsAtBottom(true);
      } else {
        console.log("NOT HIT BOTTOM");
        setIsAtBottom(false);
      }
    }, 500);
  };

  useEffect(() => {
    window.addEventListener("scroll", debounced);
    return () => {
      window.removeEventListener("scroll", debounced);
    };
  }, []);
  return isAtBottom;
}

另一个使用这个自定义钩子的组件的简化版本是

import React from "react";
import { useInfiniteScroll } from "../../hooks/useInfiniteScroll";

const Content: React.FC<ContentProps> = (props) => {
  useInfiniteScroll();
  return <div>test</div>;
};

export default Content;

每当isAtBottom 状态发生变化时,RENDERED 就会被记录,这很好。

但奇怪的部分是 RENDERED 如果我在不更改状态的情况下重播,则会再次被记录。

要重现,将滚动位置留在页面中间

  1. 将滚动条移至底部 => HIT BOTTOMRENDERED 被记录。

  2. 向上滚动一点 => NOT HIT BOTTOMRENDERED 被记录。

  3. 向上滚动一点 => NOT HIT BOTTOMRENDERED 被记录。 (这是有问题的步骤,因为我预计它不会被渲染。)

  4. 向上滚动一点 => 仅记录 NOT HIT BOTTOM

同样的事情发生在相反的状态。

为什么会渲染两次?

【问题讨论】:

    标签: javascript reactjs typescript react-hooks


    【解决方案1】:

    这似乎是 react 的默认行为,我可以在这个 stackblitz example 中重现这种行为。 我不知道为什么即使状态相同,组件也会重新渲染,您可以注意到即使我在同一个按钮上多次单击,组件也不会重新渲染超过两次。

    我认为这种行为没有任何问题,在您的应用程序中,您使用 useEffect 和一个空数组来监听滚动事件,因此您的自定义挂钩是否被多次调用并不重要。

    但是,debounce 函数的声明可以移动到 useEffect 处理程序中,在每次渲染时重新创建 debounce 函数是没有用的。

    export function useInfiniteScroll() {
      console.log("RENDERED");
      const ref = useRef<NodeJS.Timeout | null>(null);
      const [isAtBottom, setIsAtBottom] = useState<boolean>(false);
      
      useEffect(() => {
        function debounced() {
          clearTimeout(ref.current);
          
          ref.current = setTimeout(() => {
            if (isHitBottom()) {
              console.log("HIT BOTTOM");
              setIsAtBottom(true);
            } else {
              console.log("NOT HIT BOTTOM");
              setIsAtBottom(false);
            }
          }, 500);
        }
    
        window.addEventListener("scroll", debounced);
        return () => window.removeEventListener("scroll", debounced);
      }, [setIsAtBottom]);
    
      return isAtBottom;
    }
    

    【讨论】:

    • strictmode 会影响您认为的任何这种行为吗?
    猜你喜欢
    • 2020-03-17
    • 1970-01-01
    • 1970-01-01
    • 2019-09-05
    • 2020-11-05
    • 2021-11-07
    • 2019-11-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多