[英]React setState hook from scroll event listener
首先,對於類組件,這可以正常工作並且不會導致任何問題。
但是,在帶有鈎子的功能組件中,每當我嘗試從滾動事件偵聽器的函數handleScroll
設置狀態時,即使我使用了去抖動,我的狀態也無法更新或應用程序的性能會受到嚴重影響。
import React, { useState, useEffect } from "react";
import debounce from "debounce";
let prevScrollY = 0;
const App = () => {
const [goingUp, setGoingUp] = useState(false);
const handleScroll = () => {
const currentScrollY = window.scrollY;
if (prevScrollY < currentScrollY && goingUp) {
debounce(() => {
setGoingUp(false);
}, 1000);
}
if (prevScrollY > currentScrollY && !goingUp) {
debounce(() => {
setGoingUp(true);
}, 1000);
}
prevScrollY = currentScrollY;
console.log(goingUp, currentScrollY);
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<div>
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
</div>
);
};
export default App;
嘗試在handleScroll
函數中使用useCallback
鈎子,但沒有太大幫助。
我究竟做錯了什么? 如何在不影響性能的情況下從handleScroll
設置狀態?
我已經用這個問題創建了一個沙箱。
在您的代碼中,我看到了幾個問題:
1) useEffect 中的[]
意味着它不會看到任何狀態變化, goingUp
變化。 它總是會看到goingUp
初始值
2) debounce
不起作用。 它返回一個新的去抖動函數。
3)通常全局變量是一種反模式,認為它只適用於您的情況。
4)你的滾動監聽器不是被動的,正如@skyboyer 所提到的。
import React, { useState, useEffect, useRef } from "react";
const App = () => {
const prevScrollY = useRef(0);
const [goingUp, setGoingUp] = useState(false);
useEffect(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
if (prevScrollY.current < currentScrollY && goingUp) {
setGoingUp(false);
}
if (prevScrollY.current > currentScrollY && !goingUp) {
setGoingUp(true);
}
prevScrollY.current = currentScrollY;
console.log(goingUp, currentScrollY);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, [goingUp]);
return (
<div>
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
<div style={{ background: "orange", height: 100, margin: 10 }} />
</div>
);
};
export default App;
https://codesandbox.io/s/react-setstate-from-event-listener-q7to8
總之,需要添加
goingUp
作為goingUp
的依賴。
如果您使用[]
,您將只使用函數( handleScroll
,在初始渲染中創建)創建/刪除偵聽器。 換句話說,重新渲染時,滾動事件偵聽器仍在使用初始渲染時的舊handleScroll
。
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [goingUp]);
使用自定義鈎子
我建議將整個邏輯移動到自定義鈎子中,這可以使您的代碼更清晰,更易於重用。 我使用useRef
來存儲以前的值。
export function useScrollDirection() {
const prevScrollY = useRef(0)
const [goingUp, setGoingUp] = useState(false);
const handleScroll = () => {
const currentScrollY = window.scrollY;
if (prevScrollY.current < currentScrollY && goingUp) {
setGoingUp(false);
}
if (prevScrollY.current > currentScrollY && !goingUp) {
setGoingUp(true);
}
prevScrollY.current = currentScrollY;
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [goingUp]);
return goingUp ? 'up' : 'down';
}
您正在每個渲染上重新創建handleScroll
函數,因此它是指實際數據( goingup
),因此您在這里不需要useCallback
。
但是除了你重新創建handleScroll
你被設置為事件監聽器只有它的第一個實例 - 因為你給useEffect(..., [])
它只在安裝時運行一次。
一種解決方案是使用最新的handleScroll
重新訂閱scroll
。 要做到這一點,你必須在useEffect
返回清理函數(現在它返回一個更多的訂閱)並刪除[]
以在每次渲染時運行它:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
});
PS,您最好使用被動事件偵聽器進行滾動。
這是我對這種情況的解決方案
const [pageUp, setPageUp] = useState(false)
const lastScroll = useRef(0)
const checkScrollTop = () => {
const currentScroll = window.pageYOffset
setPageUp(lastScroll.current > currentScroll)
lastScroll.current = currentScroll
}
// lodash throttle function
const throttledFunc = throttle(checkScrollTop, 400, { leading: true })
useEffect(() => {
window.addEventListener("scroll", throttledFunc, false)
return () => {
window.removeEventListener("scroll", throttledFunc)
}
}, [])
僅在componentDidMount
時添加事件偵聽器應該是一次。 並非在pageUp
引起的所有更改中。 所以,我們不需要添加pageUp
到useEffect
依賴項。
Hint:
您可以添加throttledFunc
到useEffect
如果它的變化,你需要重新呈現組件
const objDiv = document.getElementById('chat_body');
objDiv.scrollTop = objDiv.scrollHeight;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.