代码之家  ›  专栏  ›  技术社区  ›  MarcoLe

react hooks-setState不更新状态属性

  •  1
  • MarcoLe  · 技术社区  · 6 年前

    我有一个来自滚动窗口对象的事件绑定。每次我滚动它都会被正确地触发。直到现在一切都很好。但是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 回复  |  直到 6 年前
        1
  •  5
  •   skyboyer    6 年前

    这是因为闭包是如何工作的。看,在初始渲染时 handleScroll 有权使用初始 navState setNavState 通过关闭。那么你就用这个版本的 手抄纸 .

    下一次呈现代码将创建 手抄纸 指的是最新的 导航器 通过关闭。但你从来不用那个版本来处理卷轴。

    看,实际上它不是你的处理程序“没有更新状态”,而是用过时的值更新它。

    选项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]);
    

    看起来效率稍高,但更凌乱/可读性较差。所以我更喜欢第一个选择。

    你可能想知道我为什么要 导航器 但不是依赖关系 设置导航状态 . 原因是-setter(回调从 useState ) is guaranteed 在每个渲染上引用相同。

    [upd]忘记了setter的功能版本。如果我们不想引用其他人的数据 土地使用权 . 所以不要错过投票 answer by giorgim

        2
  •  2
  •   Medet Tleukabiluly    6 年前

    只需为useffect添加依赖项和清理

    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>
        3
  •  2
  •   Giorgi Moniava    6 年前

    我认为你的问题可能是你注册了那个听众一次(例如 componendDidMount ),现在每次由于scroll调用listener函数时,都引用相同的值 navState 因为结束了。

    将其放在listener函数中,应该可以让您访问当前状态:

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