useCallback 没拦住重渲染:闭包里的旧值才是真凶

举报
yd_275379215 发表于 2026/11/06 08:05:06 2026/11/06
【摘要】 一个轮询组件的bug让我重新理解了useCallback。现象:定时器里每5秒上报一次数据,但上报的永远是「进入页面那一刻」的旧配置,改了刷新间隔和筛选条件都不生效。最小化后的出事代码:functionPollPanel({interval,params}:Props){const[count,setCount]=useState(0)constreport=useCallback(()=&...

一个轮询组件的 bug 让我重新理解了 useCallback。现象:定时器里每 5 秒上报一次数据,但上报的永远是「进入页面那一刻」的旧配置,改了刷新间隔和筛选条件都不生效。

最小化后的出事代码:

function PollPanel({ interval, params }: Props) {
  const [count, setCount] = useState(0)

  const report = useCallback(() => {
    // 依赖数组为空 → report 永远捕获初次渲染时的 props
    fetch('/api/report', {
      method: 'POST',
      body: JSON.stringify({ count, interval, params })
    })
  }, []) // 这里是问题

  useEffect(() => {
    const timer = setInterval(report, interval)
    return () => clearInterval(timer)
  }, [report]) // interval 变了,但 report 没变,定时器也不会重设
}

两个坑叠加了。第一个坑:依赖数组为空,JS 的闭包特性决定了 report 只能看见第一次渲染时的 count 和 props,无论外面怎么变,函数体内都是「过去」。第二个坑:useEffect 依赖了 report,而空依赖的 useCallback 返回同一个函数引用,effect 永远不会重建,interval 更新自然无效。

修复的版本:

const report = useCallback(() => {
  fetch('/api/report', {
    method: 'POST',
    body: JSON.stringify({ count, interval, params })
  })
}, [count, interval, params]) // 依赖写全,闭包就是新的

useEffect(() => {
  const timer = setInterval(report, interval)
  return () => clearInterval(timer) // report/interval 变化时清理重建
}, [report, interval])

依赖写全之后,「配置变了」会反映在闭包里,effect 也会按新的 interval 重建定时器。

如果觉得「report 每次 count 变都重建」浪费,更优雅的路线是 useRef 存最新函数,定时器只建一次:

const latest = useRef(report)
useEffect(() => { latest.current = report }) // 每次渲染后更新引用

useEffect(() => {
  const timer = setInterval(() => latest.current(), interval)
  return () => clearInterval(timer)
}, [interval]) // 定时器只依赖 interval

这轮排查沉淀了两个习惯:一是装 react-hooks/exhaustive-deps 这个 ESLint 规则,空依赖数组会给出警告,这次事故的直接原因就是这条规则被项目关掉了;二是记住 useCallback 的定位——它解决「引用稳定性」,不解决「数据新鲜度」,闭包旧值问题只能靠依赖完整性或 ref 模式解决。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。