我正在尝试编写基本的挂钩,以在滚动时获取currentScroll,lastScroll,scrollSpeed。

function useDocScroll() {
  const isClient = typeof window === "object"

  function getScroll() {
    return isClient
      ? window.pageYOffset || document.documentElement.scrollTop
      : undefined
  }

  const [docScroll, setDocScroll] = useState(getScroll)
  const [lastScroll, setLastScroll] = useState(null)
  const [scrollSpeed, setScrollSpeed] = useState(Math.abs(docScroll - lastScroll))

  useEffect(() => {
    if (!isClient) {
      return false
    }

    function handleScroll() {
      setDocScroll(getScroll())
      setLastScroll(getScroll())  // <-- why is this working?
      // setLastScroll(docScroll) // <-- why is this not working?

      setScrollSpeed(Math.abs(docScroll - lastScroll)) // <-- why is this not working?
    }

    window.addEventListener("scroll", handleScroll)
  }, [])

  return [docScroll, lastScroll, scrollSpeed]
}


看来当我执行setLastScroll(getScroll())时,它很好地保存了上一个滚动值。

但是我不明白,因为在触发handleScroll()时,getScroll()的值不应该保持不变吗?我不明白为什么setDocScroll(getScroll())setLastScroll(getScroll())具有不同的值。

另外,我以为我可以做setLastScroll(docScroll),意思是“用当前的docScroll值设置lastScroll值”,但是当docScroll值更改时,它只会打印“ 0”。

为什么是这样?我想更好地理解。

+)我无法获取由scrollSpeeddocScroll计算的lastScroll,但是我不知道如何获取这些值。

javascript - React Hooks-设置最后一个值?-LMLPHP

最佳答案

我认为您的代码为何无法正常工作是由于以下两个原因:


docScroll之后直接使用setDocScroll无效,因为setState是异步任务。无法保证在执行下一条语句之前已更新docScroll
您得到0的原因是某些特定元素内可能发生了滚动(可能)。由于document.documentElement指向html元素,因此内部没有滚动。这样您收到0


解:

您不需要多个useState。由于滚动事件发出的频率太高,所以我认为使用useReducer减少渲染数量是个好主意。重要的是要了解在根级别或某个元素内部发生滚动的位置。

对于以下解决方案,我建议:

如果滚动发生在根级别(html元素),则无需将元素传递给useDocScroll。如果在特定元素内发生滚动,则需要传递元素引用。

const initState = {
  current: 0,
  last: 0,
  speed: 0,
};

function reducer(state, action) {
  switch (action.type) {
    case "update":
      return {
        last: state.current,
        current: action.payload,
        speed: Math.abs(action.payload - state.current) || 0,
      };
    default:
      return state;
  }
}

const isClient = () => typeof window === "object";
function useDocScroll(element = document.documentElement) {
  const [{ current, last, speed }, dispatch] = useReducer(reducer, initState);

  function getScroll() {
    return isClient() ? element.scrollTop : 0;
  }

  function handleScroll() {
    dispatch({ type: "update", payload: getScroll() });
  }

  useEffect(() => {
    if (!isClient()) {
      return false;
    }

    element.addEventListener("scroll", handleScroll);

    return () => element.removeEventListener("scroll", handleScroll);
  }, []);

  return [current, last, speed];
}


例:

如果窗口内发生滚动

const {current, last, speed} = useDocScroll()


如果滚动发生在特定元素中

const {current, last, speed} = useDocScroll(document.getElementById("main"))

10-06 11:46