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

时间:2019-09-15 13:57:04

标签: javascript reactjs react-hooks

我有一个滚动窗口对象的事件绑定。每次我滚动时都会正确触发。到目前为止,一切正常。但是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钩子更新状态属性?

3 个答案:

答案 0 :(得分:5)

这是因为闭包是如何工作的。请参阅,在初始渲染时,您声明handleScroll可以通过闭包访问初始navStatesetNavState。然后,您将使用此handleScroll的#1版本滚动。

接下来渲染您的代码,以创建handleScroll的版本#2,该版本通过关闭指向最新的navState。但是您永远不要使用该版本来处理滚动。

看,实际上不是您的处理程序“未更新状态”,而是使用过时的值更新了它。

选项1

在每个渲染器上重新订阅

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

选项2

仅在数据更改时才使用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>