专题:Hooks 与状态

请添加图片描述

你给页面加了一个每秒自增的计时器,第一次从 0 变成 1,之后却一直停在 1。日志证明回调仍在执行,界面也没有报错。问题通常不在计时器精度,而在回调读取了哪一次渲染里的 count。

先理解问题

函数组件的每次渲染都有自己的状态快照。空依赖 Effect 创建的回调一直拿着初次渲染的 count;反复执行 count + 1,只是在反复要求把状态设为同一个值。需要依赖上一次状态计算下一次状态时,可以把计算交给更新函数。

实现路径

  1. 先把计时器缩到独立组件,排除导航和请求的干扰;在回调里分别记录闭包值与更新函数收到的前值。
  2. 把自增改成函数式更新,并在 Effect 清理时取消 interval。保留空依赖的前提是回调不再读取其他会变化的业务值。
  3. 如果周期由 props 决定,把周期放入依赖并在变化时重建计时器;不要为了压住 lint 提示直接删除依赖。

核心示例

import {useEffect, useState} from 'react';
import {Text} from 'react-native';
export function Ticker() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setCount(value => value + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <Text>{count}</Text>;
}

更新函数收到的是队列计算到这里的状态,因此不需要捕获旧 count。这个示例解决状态更新正确性,并不保证每一秒都准确触发。切入后台、主线程繁忙或系统调度都可能推迟回调。

容易忽略的边界

把 count 加到依赖里也能让回调看到新值,但每次更新都会拆掉并重建 interval,节奏会随执行时机改变。用于验证码或订单到期时,应另存截止时间,根据当前时间计算剩余量,不能把这个计数器直接当业务时钟。

怎么验证

  • 前台等待多个周期:数字应持续递增,而不是停在 1。
  • 卸载再进入:每个周期只递增一次,旧计时器不再输出日志。
  • 开发环境开启 Strict Mode:清理后不应留下两份 interval。

修复闭包问题的关键是区分“读取当时的值”和“根据前值计算”。前者适合保存一次操作的快照,后者才适合这里的连续自增。

适用范围与参考

示例用于说明设计与排查方法,非完整应用,也不代表已经过设备实测。 组件片段需接入对应项目;文中自定义函数、示意协议和策略数值需按业务补齐。迁移到 OHOS 时应另外核对适配层支持。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐