我有一个滚动窗口对象的事件绑定。每次我滚动时都会正确触发。到目前为止,一切正常。但是setNavState函数(这是我的setState函数)不会更新我的状态属性。
export default function TabBar() {
const [navState, setNavState] = React.useState(
{
showNavLogo: true,
lastScrollPos: 0
});
function handleScroll(e: any) {
const currScrollPos = e.path[1].scrollY;
const { lastScrollPos, showNavLogo } = navState;
console.log('currScrollPos: ', currScrollPos); // updates accordingly to the scroll pos
console.log('lastScrollPos: ', lastScrollPos); // last scroll keeps beeing 0
if (currScrollPos > lastScrollPos) {
setNavState({showNavLogo: false, lastScrollPos: currScrollPos});
} else {
setNavState({showNavLogo: true, lastScrollPos: currScrollPos});
}
}
useEffect(() => {
window.addEventListener('scroll', handleScroll.bind(this));
}, []);
...
}
所以我的问题是如何在此示例中相应地使用react钩子更新状态属性?
答案 0 :(得分:5)
这是因为闭包是如何工作的。请参阅,在初始渲染时,您声明handleScroll
可以通过闭包访问初始navState
和setNavState
。然后,您将使用此handleScroll
的#1版本滚动。
接下来渲染您的代码,以创建handleScroll
的版本#2,该版本通过关闭指向最新的navState
。但是您永远不要使用该版本来处理滚动。
看,实际上不是您的处理程序“未更新状态”,而是使用过时的值更新了它。
在每个渲染器上重新订阅
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
});
仅在数据更改时才使用useCallback
重新创建处理程序,并且仅在重新创建回调后才重新预订
const handleScroll = useCallback(() => { ... }, [navState]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
看起来效率更高,但更混乱/可读性更差。所以我更喜欢第一选择。
您可能想知道为什么我将navState
包括在依赖项中,而不包括setNavState
。原因是-setter(从useState
返回的回调)is guaranteed在每个渲染器上都是相同的。
[UPD]忘记了设置器的功能版本。当我们不想引用另一个useState
的数据时,它肯定可以正常工作。因此,不要错过投票answer by giorgim
答案 1 :(得分:2)
我认为您的问题可能是您一次注册了该侦听器(例如,像componendDidMount
),现在每次由于滚动而调用该侦听器函数时,您所指的是navState
的相同值因为关闭。
将其放在您的侦听器函数中,应该可以让您访问当前状态:
setNavState(ps => {
if (currScrollPos > ps.lastScrollPos) {
return { showNavLogo: false, lastScrollPos: currScrollPos };
} else {
return { showNavLogo: true, lastScrollPos: currScrollPos };
}
});
答案 2 :(得分:2)
只需添加依赖和清理useEffect
function TabBar() {
const [navState, setNavState] = React.useState(
{
showNavLogo: true,
lastScrollPos: 0
});
function handleScroll(e) {
const currScrollPos = e.path[1].scrollY;
const { lastScrollPos, showNavLogo } = navState;
console.log('showNavLogo: ', showNavLogo);
console.log('lastScrollPos: ', lastScrollPos);
if (currScrollPos > lastScrollPos) {
setNavState({showNavLogo: false, lastScrollPos: currScrollPos});
} else {
setNavState({showNavLogo: true, lastScrollPos: currScrollPos});
}
}
React.useEffect(() => {
window.addEventListener('scroll', handleScroll.bind(this));
return () => {
window.removeEventListener('scroll', handleScroll.bind(this));
}
}, [navState]);
return (<h1>scroll example</h1>)
}
ReactDOM.render(<TabBar />, document.body)
h1 {
height: 1000px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>