简体   繁体   English

反应挂钩-setState不更新状态属性

[英]React hooks - setState does not update state properties

I have a event binding from the window object on scroll. 我有一个滚动窗口对象的事件绑定。 It gets properly fired everytime I scroll. 每次我滚动时都会正确触发。 Until now everything works fine. 到目前为止,一切正常。 But the setNavState function (this is my setState-function) does not update my state properties. 但是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));
    }, []);

   ...
   }

So my question is how do I update my state properties with react hooks in this example accordingly? 所以我的问题是,如何在此示例中使用react hooks更新我的状态属性?

it's because how closure works. 这是因为关闭是如何工作的。 See, on initial render you're declaring handleScroll that has access to initial navState and setNavState through closure. 请参阅,在初始渲染时,您声明的是handleScroll ,它可以通过闭包访问初始navStatesetNavState Then you're subscribing for scroll with this #1 version of handleScroll . 然后,您将使用此#1版本的handleScroll预订滚动。

Next render your code creates version #2 of handleScroll that points onto up to date navState through closure. 接下来渲染您的代码,创建handleScroll #2版本,该版本通过闭包指向最新的navState But you never use that version for handling scroll. 但是您永远不要使用该版本来处理滚动。

See, actually it's not your handler "did not update state" but rather it updated it with outdated value. 看,实际上这不是您的处理程序“未更新状态”,而是使用过时的值对其进行了更新。

Option 1 选项1

Re-subscribing on each render 重新订阅每个渲染

useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
});

Option 2 选项2

Utilizing useCallback to re-create handler only when data is changed and re-subscribe only if callback has been recreated 仅在更改数据时才使用useCallback重新创建处理程序,并且仅在重新创建回调后才重新订阅

const handleScroll = useCallback(() => { ... }, [navState]);
useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);

Looks slightly more efficient but more messy/less readable. 看起来效率更高,但更混乱/可读性更差。 So I'd prefer first option. 所以我更喜欢第一选择。

You may wonder why I include navState into dependencies but not setNavState . 您可能想知道为什么我将navState包含在依赖项中,而不setNavState The reason is - setter(callback returned from useState ) is guaranteed to be referentially same on each render. 原因是- 确保 setter(从useState返回的回调)在每个渲染器上都具有相同的参照关系。

[UPD] forgot about functional version of setter. [UPD]忘记了二传手的功能版本。 It will definitely work fine while we don't want to refer data from another useState . 当我们不想从另一个useState引用数据时,它肯定可以正常工作。 So don't miss up-voting answer by giorgim 所以不要错过giorgim的投票表决答案

I think your problem could be that you registered that listener once (eg like componendDidMount ), now every time that listener function gets called due to scroll, you are referring to the same value of navState because of the closure. 我认为您的问题可能是您一次注册了该侦听器(例如,像componendDidMount ),现在每次由于滚动调用侦听器函数时,由于关闭,您都引用了相同的navState值。

Putting this in your listener function instead, should give you access to current state: 相反,将其放在您的侦听器函数中,应该可以使您访问当前状态:

setNavState(ps => {
      if (currScrollPos > ps.lastScrollPos) {
        return { showNavLogo: false, lastScrollPos: currScrollPos };
      } else {
        return { showNavLogo: true, lastScrollPos: currScrollPos };
      }
    });

Just add dependency and cleanup for useEffect 只需添加依赖项并清除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> 

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

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