简体   繁体   English

React Hooks - 设置最后一个值?

[英]React Hooks - set last value?

I'm trying to write basic hooks to get currentScroll, lastScroll, scrollSpeed while scrolling.我正在尝试编写基本的钩子来在滚动时获取 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]
}

It seems like when I do setLastScroll(getScroll()) , it saves the last scroll value well.似乎当我执行setLastScroll(getScroll()) ,它可以很好地保存最后一个滚动值。

But I don't understand because when handleScroll() is firing, shouldn't getScroll() value stays the same?但我不明白,因为当handleScroll()触发时, getScroll()值不应该保持不变吗? I don't get it why setDocScroll(getScroll()) and setLastScroll(getScroll()) have different value.我不明白为什么setDocScroll(getScroll())setLastScroll(getScroll())有不同的值。

Also, I thought I could do setLastScroll(docScroll) , meaning 'set lastScroll value with current docScroll value', but it just prints '0' while docScroll value changes.另外,我想我可以做setLastScroll(docScroll) ,意思是“用当前的 docScroll 值设置 lastScroll 值”,但它只是在 docScroll 值更改时打印“0”。

Why is this?为什么是这样? I want to understand better.我想更好地理解。

+) And I can't get scrollSpeed which is calculated by docScroll and lastScroll , but I don't know how to get those values. +) 而且我无法获得由docScrolllastScroll计算的scrollSpeed ,但我不知道如何获得这些值。

编辑分心-golick-c89w3

I think why your code not working is because of following two reasons:我认为为什么您的代码不起作用是因为以下两个原因:

  1. using docScroll directly after setDocScroll won't work because setState is asynchronous task.setDocScroll之后直接使用docScroll将不起作用,因为 setState 是异步任务。 There is no guarantee that docScroll is updated before executing next statement不能保证在执行下docScroll语句之前更新docScroll
  2. you are getting 0 because of scrolling happening inside some particular element (probably).由于在某些特定元素(可能)内发生滚动,您得到0 Since document.documentElement points to html element and there is no scrolling inside it.由于document.documentElement指向html元素并且里面没有滚动。 So you receive 0所以你收到0

Solution:解决方案:

You don't need multiple useState s.您不需要多个useState Since scroll events emits too frequently, i think its good idea to use useReducer to reduce number of renders.由于滚动事件发射太频繁,我认为使用useReducer来减少渲染次数是个好主意。 It is important understand where scrolling happening whether on root level or inside some element.无论是在根级别还是在某个元素内部,了解滚动发生的位置很重要。

For below solution i proposed:对于以下解决方案,我提出了:

if scroll happening on root level (html element) no need to pass element to useDocScroll .如果滚动发生在根级别(html 元素),则无需将元素传递给useDocScroll If scroll happening inside particular element, you need to pass element reference.如果滚动发生在特定元素内,则需要传递元素引用。

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];
}

Example:例子:

if scroll happening inside window如果滚动发生在窗口内

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

if scroll happening in particular element如果滚动发生在特定元素

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

It not working due to closures : 由于关闭,它不起作用:

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

  function handleScroll() {
    setDocScroll(getScroll());

    // On calling handleScroll the values docScroll & lastScroll
    // are always will be of the first mount,
    // the listener "remembers" (closures) such values and never gets updated
    setLastScroll(docScroll);
    setScrollSpeed(Math.abs(docScroll - lastScroll));

    // v Gets updated on every call
    setLastScroll(getScroll());
  }

  // v Here you assigning a callback which closes upon the lexical scope of
  // docScroll and lastScroll
  window.addEventListener('scroll', handleScroll);
}, []);

To fix it, a possible solution can be a combination of a reference ( useRef ) and functional setState .要修复它,一个可能的解决方案可以是参考( useRef )和功能setState

For example:例如:

setScrollSpeed(lastScroll => Math.abs(getScroll() - lastScroll))

Not sure why lastScrolled needed:不知道为什么lastScrolled需要:

function useDocScroll() {
  const [docScroll, setDocScroll] = useState(getScroll());
  const lastScrollRef = useRef(null);
  const [scrollSpeed, setScrollSpeed] = useState();

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

    function handleScroll() {
      const lastScroll = lastScrollRef.current;
      const curr = getScroll();
      setScrollSpeed(Math.abs(getScroll() - (lastScroll || 0)));
      setDocScroll(curr);

      // Update last
      lastScrollRef.current = curr;
    }

    window.addEventListener('scroll', handleScroll);
  }, []);

  return [docScroll, lastScrollRef.current, scrollSpeed];
}

编辑 hidden-architecture-p8xbi

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM