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>;
  }
}
相关推荐
xjt_090113 分钟前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农24 分钟前
Vue 2.3
前端·javascript·vue.js
夜郎king1 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳1 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵2 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星2 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_2 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝2 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions2 小时前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发2 小时前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法