[英]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. +) 而且我无法获得由
docScroll
和lastScroll
计算的scrollSpeed
,但我不知道如何获得这些值。
I think why your code not working is because of following two reasons:我认为为什么您的代码不起作用是因为以下两个原因:
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 statementdocScroll
语句之前更新docScroll
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];
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.