You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中useCallback结合resize监听时state不更新问题排查

问题:useCallback依赖更新后,resize事件中的状态仍未更新?

我编写了一个useDeviceDetect钩子,可正常根据屏幕宽度判断当前设备是桌面端、平板还是移动端。现在在组件中需要仅在移动端计算某个元素的高度,同时监听window的resize事件以应对文本换行导致的高度变化,但组件内的handleWindowSizeChange函数里,isDesktop始终保持初始值,即便使用了useCallback钩子也无法解决。

组件代码如下:

const handleWindowSizeChange = useCallback(() => {
    console.log(isDesktop)  // 若初始为桌面端则始终为true,初始为移动端则始终为false
}, [isDesktop]);

useEffect(() => {
   console.log(isDesktop) // 触发断点时会输出正确值
}, [isDesktop]);

useEffect(() => {
  if (!loaded) return;
  handleWindowSizeChange();
  window.addEventListener('resize', handleWindowSizeChange);
        
  return () => {
     window.removeEventListener('resize', handleWindowSizeChange);
  }
}, [loaded]);

设备检测钩子代码如下:

'use client'
import { useState, useEffect } from 'react';
import { throttle } from './utils';

export default function useDeviceDetect() {
    const [isMobile, setMobile] = useState(false);
    const [isTablet, setTablet] = useState(false);
    const [isDesktop, setDesktop] = useState(false);
    const [loaded, setLoaded] = useState(false);

    const breakpointSmall = 480;
    const breakpointMedium = 960;
    const breakpointLarge = 1280;
    const breakpointXLarge = 1680;

    function handleWindowSizeChange() {
        if (window.screen.width < breakpointSmall) {
            setMobile(true);
            setTablet(false);
            setDesktop(false);
        }
        if (window.screen.width >= breakpointSmall && window.screen.width < breakpointMedium) {
            setMobile(false);
            setTablet(true);
            setDesktop(false);
        }

        if (window.screen.width >= breakpointMedium && window.screen.width < breakpointLarge) {
            setMobile(false);
            setTablet(false);
            setDesktop(true);
        }

        if (window.screen.width >= breakpointLarge && window.screen.width < breakpointXLarge) {
            setMobile(false);
            setTablet(false);
            setDesktop(true);
        }

        if (window.screen.width >= breakpointXLarge) {
            setMobile(false);
            setTablet(false);
            setDesktop(true);
        }

        setLoaded(true);
    }

    useEffect(() => {
        handleWindowSizeChange();
        window.addEventListener('resize', handleWindowSizeChange);
        return () => {
            window.removeEventListener('resize', handleWindowSizeChange);
        }
    }, []);

    return { isMobile, isTablet, isDesktop, loaded };
}

我理解useCallback会缓存函数,并在依赖项变化时重新创建,原以为该钩子可以解决类似setInterval中state不更新的问题。请问我是代码实现有误,还是误解了useCallback的用途?


问题原因

你对useCallback的理解没错,它确实会在依赖变化时重新生成函数实例,但问题出在resize事件的监听没有跟随函数更新重新绑定。

你只在loaded变化时执行了一次事件绑定(也就是组件初始加载时),后续isDesktop更新导致handleWindowSizeChange重新创建后,新的函数并没有被绑定到resize事件上,window上始终挂着第一次的旧函数——而旧函数的闭包中捕获的是初始的isDesktop值,所以无论后续状态怎么变,旧函数输出的都是初始值。

修复方案

方案一:更新事件监听的依赖项,绑定最新的函数实例

修改组件中负责绑定resize事件的useEffect,把handleWindowSizeChange加入依赖数组,这样每次函数更新时,都会移除旧的监听,绑定新的函数:

useEffect(() => {
  if (!loaded) return;
  handleWindowSizeChange();
  window.addEventListener('resize', handleWindowSizeChange);
        
  return () => {
     window.removeEventListener('resize', handleWindowSizeChange);
  }
}, [loaded, handleWindowSizeChange]); // 新增handleWindowSizeChange作为依赖

方案二:简化逻辑,直接依赖设备状态变化执行操作

既然useDeviceDetect已经在监听resize并更新设备状态,你可以直接依赖isMobile的变化来触发高度计算,不需要额外绑定resize事件:

// 移除单独的resize监听,直接在设备状态变化时执行逻辑
useEffect(() => {
  if (!loaded) return;
  if (!isMobile) return; // 仅在移动端执行
  // 这里写入计算元素高度的逻辑
}, [loaded, isMobile]);

如果必须监听resize(比如文本换行导致高度变化,但设备类型未改变),可以在handleWindowSizeChange中直接通过window.screen.width判断设备类型,或者确保事件绑定的是最新的函数实例(方案一)。


内容的提问来源于stack exchange,提问作者MPN7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 16:40:15