跳到主要内容

闭包与定时器

怎样判断回调应该使用哪个时刻的数据?​

  • 事件快照: 延迟发送“点击时”的内容应保留当时值,完整闭包模型见 state 快照;
  • 累计更新: 定时自增需要从待处理状态计算下一值,使用更新函数,规则见排队更新 state;
  • 最新上下文: Effect 建立的定时任务若要读最新主题、回调等,不希望因此重建计时器,可用 Effect Event;

如何封装可以暂停且不会因回调变化重启的定时器?​

  • 生命周期: delay 决定计时器是否存在与执行间隔,delay 为 null 时暂停,每次重建前清理旧计时器;
  • 非响应式回调: onTick 在触发时读取最新 callback,callback 改变本身不重置倒计时;如果业务希望重启,就应把它作为依赖;
import { useEffect, useEffectEvent, useState } from "react";

function useInterval(callback, delay) {
const onTick = useEffectEvent(callback);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => onTick(), delay);
return () => clearInterval(id);
}, [delay]);
}

export default function Clock() {
const [seconds, setSeconds] = useState(0);
const [running, setRunning] = useState(true);
useInterval(() => setSeconds((value) => value + 1), running ? 1000 : null);
return (
<button onClick={() => setRunning((value) => !value)}>
{seconds} 秒,点击{running ? "暂停" : "继续"}
</button>
);
}

为什么不能在渲染中把最新 state 写入 ref?​

  • 提交一致性: 渲染可能被放弃,把尚未提交的值写到共享 ref 会让外部回调看见与当前界面不一致的数据;
  • 正确归属: ref 保存句柄、Effect Event 读取 Effect 的最新上下文、state 驱动 UI,各自按其契约使用;
  • 初始化区别: 创建昂贵纯对象的受控惰性初始化是专门例外,不等同于每次渲染同步 state 到 ref;