React Hooks,如何实现 useHideOnScroll 钩子?

IT技术 reactjs state react-hooks
2021-05-08 22:51:02
const shouldHide = useHideOnScroll();
return shouldHide ? null : <div>something</div>

useHideOnScroll 行为不应在每次滚动时返回更新值,而应仅在发生更改时返回更新值。

伪逻辑类似于以下内容:

if (scrolledDown && !isHidden) {
        setIsHidden(true);
      } else if (scrolledUp && isHidden) {
        setIsHidden(false);
      }

换句话说,如果向下滚动且未隐藏,则隐藏。如果向上滚动并隐藏,则取消隐藏。但是如果向下滚动并隐藏,什么都不做,或者向上滚动而不隐藏,什么都不做。

你如何用钩子实现它?

4个回答

在 React(16.8.0+) 中使用钩子


import React, { useState, useEffect } from 'react'

function getWindowDistance() {
    const { pageYOffset: vertical, pageXOffset: horizontal } = window
    return {
        vertical,
        horizontal,
    }
}

export default function useWindowDistance() {
    const [windowDistance, setWindowDistance] = useState(getWindowDistance())

    useEffect(() => {
        function handleScroll() {
            setWindowDistance(getWindowDistance())
        }

        window.addEventListener('scroll', handleScroll)
        return () => window.removeEventListener('scroll', handleScroll)
    }, [])

    return windowDistance
}

您需要使用 window.addEventListener 和https://reactjs.org/docs/hooks-custom.html指南。

那是我的工作示例:

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

const useHideOnScrolled = () => {
  const [hidden, setHidden] = useState(false);

  const handleScroll = () => {
    const top = window.pageYOffset || document.documentElement.scrollTop;
    setHidden(top !== 0);
  };

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

  return hidden;
};

export default useHideOnScrolled;

现场演示:https : //codesandbox.io/s/w0p3xkoq2l?fontsize=14

我认为名字useIsScrolled()或类似的东西会更好

这里:

const useHideOnScroll = () => {
  const prevScrollY = React.useRef<number>();
  const [isHidden, setIsHidden] = React.useState(false);

  React.useEffect(() => {
    const onScroll = () => {
      setIsHidden(isHidden => {
        const scrolledDown = window.scrollY > prevScrollY.current!;
        if (scrolledDown && !isHidden) {
          return true;
        } else if (!scrolledDown && isHidden) {
          return false;
        } else {
          prevScrollY.current = window.scrollY;
          return isHidden;
        }
      });
    };

    console.log("adding listener");
    window.addEventListener("scroll", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
    };
  }, []);

  return isHidden;
};

const Navbar = () => {
  const isHidden = useHideOnScroll();
  console.info("rerender");
  return isHidden ? null : <div className="navbar">navbar</div>;
};

export default Navbar;

您可能会担心会setIsHidden导致在 each 上onScroll重新渲染,因为它总是返回一些新的状态值,但是 setter fromuseState足够聪明,只有在值实际更改时才更新。

此外,您的.navbar我已向其中添加了一个类)不应在布局出现时更改布局,否则您的代码段将被锁定在无限循环中。这里也有合适的样式:

.navbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 30px;
  background: rgba(255, 255, 255, 0.8);
}

完整的 CodeSandbox:https ://codesandbox.io/s/13kr4xqrwq

经过几个小时的悬垂,这就是我想出的。

const useHideOnScroll = () => {
  const [isHidden, setIsHidden] = useState(false);
  const prevScrollY = useRef<number>();

  useEffect(() => {
    const onScroll = () => {
      const scrolledDown = window.scrollY > prevScrollY.current!;
      const scrolledUp = !scrolledDown;

      if (scrolledDown && !isHidden) {
        setIsHidden(true);
      } else if (scrolledUp && isHidden) {
        setIsHidden(false);
      }

      prevScrollY.current = window.scrollY;
    };

    window.addEventListener("scroll", onScroll);
    return () => {
      window.removeEventListener("scroll", onScroll);
    };
  }, [isHidden]);

  return isHidden;
};

用法:

const shouldHide = useHideOnScroll();
return shouldHide ? null : <div>something</div>

它仍然是次优的,因为我们重新分配onScrollisHidden更改的时间。其他一切都感觉太老套和无证。我真的很想找到一种方法来做同样的事情,而无需重新分配onScroll. 如果您知道方法,请发表评论:)