首先,使用一个类组件,它可以很好地工作并且不会引起任何问题。
但是,在带有钩子的功能组件中,每当我尝试通过滚动事件侦听器的功能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;
试图在useCallback
函数中使用handleScroll
钩子,但并没有太大帮助。
我在做什么错?如何在不影响性能的情况下从handleScroll
设置状态?
我为此创建了一个沙箱。
答案 0 :(得分:1)
您正在每个渲染器上重新创建handleScroll
函数,因此它引用的是实际数据(goingup
),因此您在这里不需要useCallback
。
但是,除了重新创建handleScroll
之外,还将其设置为事件侦听器,只是它的第一个实例-因为您给useEffect(..., [])
设置了它在挂载时仅运行一次。
一种解决方案是使用最新的scroll
重新订阅handleScroll
。为此,您必须在useEffect
之内返回cleanup函数(现在它又返回一个订阅),并删除[]
以在每个渲染器上运行它:
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
});
PS,最好使用passive event listener进行滚动。
答案 1 :(得分:1)
在您的代码中,我看到了几个问题:
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
答案 2 :(得分:1)
简而言之,您需要添加
goingUp
作为useEffect的依赖项。
如果使用[]
,则将仅使用功能(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';
}
答案 3 :(得分:0)
这是我针对这种情况的解决方案
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
到{{1}},并且需要重新渲染组件
答案 4 :(得分:0)
const objDiv = document.getElementById('chat_body');
objDiv.scrollTop = objDiv.scrollHeight;