Bug随记 —— 一个定时器引发的Bug

一个定时器引发的Bug

今天遇到一个这个样的 bug,我在一个页面使用定时器轮训订单状态,当订单状态改变时,进行路由跳转;在页面销毁时清理掉定时器。代码如下:

ts 复制代码
let timer: ReturnType<typeof setInterval> | null = setInterval(() => {
  queryOrderState();
}, 3000);
onBeforeUnmount(() => {
  timer = null;
  clearInterval(timer);
});

问题:定时器没有被清理掉,然后跳转到其他页面后一直往订单成功页面跳转。

原因:清理定时器时先对timer进行了赋值导致清理时,执行的是clearInterval(null);定时器一直在没有清理掉。

解决:

js 复制代码
onBeforeUnmount(() => {
  if (timer !== null) {
    clearInterval(timer);
    timer = null;
  }
});

其实这个问题仔细一点都可以很快的确认 bug 出现的地方和原因。但我当时手边正好有一个 react 搭建的 demo 项目,我就在 react 中试着复现一下这个 bug,却出现了另一个 bug,代码如下:

tsx 复制代码
const HomePage: FC<HomePageCopm> = () => {
  let timer: any = setInterval(() => {
    console.log('3434');
  }, 1000);
  useEffect(() => {
    return () => {
      clearInterval(timer);
      timer = null;
    };
  }, []);
  return <div></div>;
};

如上代码,当路由跳转后,定时器却没有清理掉,这里我是先把timer = null赋值放在清理定时器之后了,也无法清理掉定时器;我又改变了一下代码结构:

tsx 复制代码
const HomePage: FC<HomePageCopm> = () => {
  useEffect(() => {
    let timer: any = setInterval(() => {
      console.log('3434');
    }, 1000);
    return () => {
      clearInterval(timer);
      timer = null;
    };
  }, []);
  return <div></div>;
};

这样就可以清理掉了,这是为什么呢?

原因:React 组件中每一次渲染或者更新,都是会重新创建整个节点,这样一些引用类型数据(比如:函数、定时器、对象)的地址指向都会改变,这样就相当于每次组件重新更新渲染时,都会对定时器赋值一个新的地址,这样就会导致可能页面清理定时器时只清理掉了一个,还存在其他定时器在执行。

解决:

  • 可以使用useRef, useRef定义的数据在每次组件更新时不会被重新定义/清空。
tsx 复制代码
const HomePage: FC<HomePageCopm> = () => {
  const timerRef = useRef(null);
  timerRef.current = setInterval(() => {
    console.log('3434');
  }, 1000);
  useEffect(() => {
    return () => {
      clearInterval(timer);
      timer = null;
    };
  }, []);
  return <div></div>;
};
  • 或者把 timer 变量定义时和清理定时器放在同一个作用域内,这样也可以清理掉;
ts 复制代码
const HomePage: FC<HomePageCopm> = () => {
  useEffect(() => {
    let timer: any = setInterval(() => {
      console.log('3434');
    }, 1000);
    return () => {
      clearInterval(timer);
      timer = null;
    };
  }, []);
  return <div></div>;
};

反思:为什么 Vue 中可以通过生命周期清理掉定时器,React 中却不行? 答:Vue中可以通过生命周期清理掉定时器,是因为Vue的组件是一个持久存在的实例对象setup()data()中定义的变量在整个组件生命周期内是稳定的,不会因视图更新而重新创建。 当组件被销毁时,相关的定时器变量仍然在作用域中,因此在onBeforeUnmount()中可以准确地清除定时器。

React的函数组件本质上是一个函数,每次渲染都会重新执行该函数,重新创建局部变量 。如果你在组件函数体(useEffect 外)里定义定时器变量,会因为多次 render 而不断创建新的定时器,而useEffect的清理函数只能访问到初始渲染时那个作用域中的定时器变量,无法清除后续 render 创建的那些定时器,导致定时器"清不掉"。

在 React 中使用类组件应该就不会存在这样问题了:

tsx 复制代码
class HomePage extends React.Component {
  timer: any = null;
  componentDidMount(){
      this.timer =  setInterval(() => {
      console.log('tick');
    }, 1000);
  }
  componentWillUnmount(){
    clearInterval(this.timer);
    this.timer = null;
  }
  render() {
    return <div>Home</div>;
  }
}
相关推荐
程序员爱钓鱼4 分钟前
Node.js 编程实战:测试与调试 —— 日志与监控方案
前端·后端·node.js
Mapmost13 分钟前
数字孪生项目效率翻倍!AI技术实测与场景验证实录
前端
小酒星小杜17 分钟前
在AI时代,技术人应该每天都要花两小时来构建一个自身的构建系统-Input篇
前端·程序员·架构
Cache技术分享25 分钟前
290. Java Stream API - 从文本文件的行创建 Stream
前端·后端
陈_杨27 分钟前
前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP--ArkTS 卡片开发完全指南
前端·harmonyos
小杨同学4933 分钟前
C 语言实战:枚举类型实现数字转星期(输入 1~7 对应星期几)
前端·后端
陈_杨34 分钟前
前端成功转鸿蒙开发者真实案例,教大家如何开发鸿蒙APP--ArkTS 卡片刷新机制
前端·harmonyos
go_caipu42 分钟前
Vben Admin管理系统集成qiankun微服务(二)
前端·javascript
幻云20101 小时前
Next.js指南:从入门到精通
开发语言·javascript·人工智能·python·架构
唐叔在学习1 小时前
insertAdjacentHTML踩坑实录:AI没搞定的问题,我给搞定啦
前端·javascript·html