当前位置: 首页 > news >正文

HOW - 计时器实践和注意事项

目录

  • 一、前言
  • 二、具体介绍
    • 1. 清理定时器
    • 2. 避免使用过多的定时器
    • 3. 处理定时器的延迟和准确性
    • 4. 避免在定时器中访问过期的状态
    • 5. 考虑使用 `requestAnimationFrame`
    • 6. 注意组件的生命周期
      • 示例 1: 使用 `useEffect` 清理定时器
      • 示例 2: 避免在重新渲染时创建重复的定时器
      • 示例 3: 动态依赖的定时器
    • 7. 避免阻塞主线程
    • 总结
  • 三、页面进入后台和重新回到前台场景
    • 1. 浏览器优化
    • 2. 使用 `requestAnimationFrame`
    • 总结

一、前言

在HOW - 前端定时器实践(含防抖、interval 模拟)中我们简单了解过 JavaScript 中的计时器以及潜在问题,包括防抖和节流的应用。那今天我们进一步来学习计时器实践和注意事项。

二、具体介绍

在前端项目中使用 setTimeoutsetInterval 执行定时任务是很常见的,但在实践中需要注意以下几个方面,以确保代码的正确性和性能:

1. 清理定时器

在组件卸载或不再需要定时器时,务必清理定时器,以避免内存泄漏和不必要的计算:

比如在 React 项目中,使用 clearTimeoutclearInterval: 在组件的 useEffect 钩子中,可以返回一个清理函数来清除定时器。

示例:

import React, { useEffect } from 'react';const TimerComponent: React.FC = () => {useEffect(() => {const timerId = setInterval(() => {console.log('Interval task');}, 1000);return () => clearInterval(timerId); // 清理定时器}, []);return <div>Timer Component</div>;
};

2. 避免使用过多的定时器

大量使用定时器可能会导致性能问题,特别是在浏览器中。如果你需要多个定时任务,考虑合并它们或者使用更合适的机制,如 requestAnimationFrame 或者 Web Workers

3. 处理定时器的延迟和准确性

  • setTimeoutsetInterval 的延迟不一定精确: 特别是在 CPU 使用率很高的情况下,定时器可能会有延迟。setInterval 的实际间隔可能会比预期长。

  • 适当使用 Date.now()performance.now(): 如果你需要更精确的时间管理,可以使用 Date.now()performance.now() 来计算实际的时间间隔。

示例:

const timerId = setInterval(() => {const now = performance.now();console.log(`Interval at ${now}`);
}, 1000);

4. 避免在定时器中访问过期的状态

如果定时器引用了组件的状态或属性,确保在定时器执行时这些引用仍然有效。否则,你可能会遇到状态过期的问题。

例如,在 React 项目中,使用 useRef 或在清理函数中取消定时器可以避免这种问题。

示例:

import React, { useEffect, useRef } from 'react';const TimerComponent: React.FC = () => {const countRef = useRef(0);useEffect(() => {const timerId = setInterval(() => {countRef.current += 1;console.log(countRef.current);}, 1000);return () => clearInterval(timerId);}, []);return <div>Timer Component</div>;
};

5. 考虑使用 requestAnimationFrame

对于需要频繁更新 UI 或进行动画的任务,使用 requestAnimationFrame 可能更合适。它会在浏览器下一次重绘之前调用回调函数,从而提供更流畅的动画效果。

示例:

import React, { useEffect, useRef } from 'react';const AnimationComponent: React.FC = () => {const requestIdRef = useRef<number>(0);const animate = () => {console.log('Animation frame');requestIdRef.current = requestAnimationFrame(animate);};useEffect(() => {requestIdRef.current = requestAnimationFrame(animate);return () => cancelAnimationFrame(requestIdRef.current); // 清理动画帧}, []);return <div>Animation Component</div>;
};

6. 注意组件的生命周期

确保定时器不会在组件的生命周期中导致问题,例如在组件卸载时定时器还在运行,或者在组件重新渲染时创建了重复的定时器。

当然!确保定时器在组件的生命周期内不会导致问题是很重要的。以下是一些代码示例,展示了如何在 React 组件中正确使用和清理定时器,以避免在组件卸载时定时器仍在运行,或者在组件重新渲染时创建重复的定时器。

示例 1: 使用 useEffect 清理定时器

useEffect 中设置定时器,并在组件卸载时清理定时器:

import React, { useEffect, useState } from 'react';const TimerComponent: React.FC = () => {const [count, setCount] = useState(0);useEffect(() => {// 创建定时器const timerId = setInterval(() => {setCount(prevCount => prevCount + 1);}, 1000);// 清理定时器return () => {clearInterval(timerId);};}, []); // 依赖数组为空,确保定时器只在组件挂载时创建一次return <div>Count: {count}</div>;
};

示例 2: 避免在重新渲染时创建重复的定时器

useEffect 中管理定时器,确保每次重新渲染时不会创建新的定时器:

import React, { useEffect, useRef, useState } from 'react';const TimerComponent: React.FC = () => {const [count, setCount] = useState(0);const timerRef = useRef<NodeJS.Timeout | null>(null);useEffect(() => {// 创建定时器timerRef.current = setInterval(() => {setCount(prevCount => prevCount + 1);}, 1000);// 清理定时器return () => {if (timerRef.current) {clearInterval(timerRef.current);}};}, []); // 依赖数组为空,确保定时器只在组件挂载时创建一次return <div>Count: {count}</div>;
};

示例 3: 动态依赖的定时器

如果定时器的行为依赖于组件的 props 或 state,可以在 useEffect 中添加依赖,并确保每次更新时清理旧的定时器:

import React, { useEffect, useRef, useState } from 'react';const TimerComponent: React.FC<{ delay: number }> = ({ delay }) => {const [count, setCount] = useState(0);const timerRef = useRef<NodeJS.Timeout | null>(null);useEffect(() => {// 清理上一个定时器(如果存在)if (timerRef.current) {clearInterval(timerRef.current);}// 创建新的定时器timerRef.current = setInterval(() => {setCount(prevCount => prevCount + 1);}, delay);// 清理定时器return () => {if (timerRef.current) {clearInterval(timerRef.current);}};}, [delay]); // 当 delay 改变时重新创建定时器return <div>Count: {count}</div>;
};

这些实践有助于避免在组件卸载时定时器仍在运行,或在组件重新渲染时创建重复的定时器,从而确保组件行为的正确性和性能。

7. 避免阻塞主线程

避免在定时器中执行耗时的操作,这样可以防止阻塞主线程,导致应用响应变慢。

总结

在前端项目中使用 setTimeoutsetInterval 时,要注意清理定时器、避免过多使用、处理定时器的延迟和准确性、避免访问过期的状态、选择合适的定时方法(如 requestAnimationFrame)、考虑组件的生命周期,以及避免阻塞主线程。通过遵循这些实践,可以提高代码的可靠性和性能。

三、页面进入后台和重新回到前台场景

有个更进一步的问题,在页面进入后台或者重新回到前台,定时器的工作机制?

当页面进入后台(即不可见)或重新回到前台(即可见)时,定时器的工作机制可能会受到浏览器的影响。

浏览器通常会对不可见的页面进行性能优化,以减少资源消耗,这会影响 setTimeoutsetInterval 的行为。

以下是一些关键点和建议,以确保定时器在这些情况下能够正确工作:

1. 浏览器优化

  • 页面不可见时的性能优化: 许多现代浏览器会在页面不可见时暂停定时器(例如,setInterval)或降低其调用频率。这是为了节省资源和电池寿命。

  • 文档可见性 API: 使用 document.visibilityStatevisibilitychange 事件来检测页面是否可见,并根据需要调整定时器的行为。

示例:

import React, { useEffect, useRef, useState } from 'react';const TimerComponent: React.FC = () => {const [count, setCount] = useState(0);const timerRef = useRef<NodeJS.Timeout | null>(null);const handleVisibilityChange = () => {if (document.visibilityState === 'visible') {// 页面重新变为可见,重新启动定时器if (!timerRef.current) {timerRef.current = setInterval(() => {setCount(prevCount => prevCount + 1);}, 1000);}} else {// 页面变为不可见,清理定时器if (timerRef.current) {clearInterval(timerRef.current);timerRef.current = null;}}};useEffect(() => {// 设置定时器timerRef.current = setInterval(() => {setCount(prevCount => prevCount + 1);}, 1000);// 监听可见性变化document.addEventListener('visibilitychange', handleVisibilityChange);// 清理return () => {if (timerRef.current) {clearInterval(timerRef.current);}document.removeEventListener('visibilitychange', handleVisibilityChange);};}, []);return <div>Count: {count}</div>;
};

2. 使用 requestAnimationFrame

requestAnimationFrame 会在页面的下一次重绘前调用回调函数,并且当页面不可见时,浏览器会暂停 requestAnimationFrame 调用。这对于动画任务是有利的,但如果需要持续运行的定时任务,可能需要结合其他机制。

示例:

import React, { useEffect, useRef, useState } from 'react';const AnimationComponent: React.FC = () => {const [count, setCount] = useState(0);const requestIdRef = useRef<number>(0);const animate = () => {setCount(prevCount => prevCount + 1);requestIdRef.current = requestAnimationFrame(animate);};useEffect(() => {requestIdRef.current = requestAnimationFrame(animate);// 页面不可见时停止动画const handleVisibilityChange = () => {if (document.visibilityState === 'visible') {requestIdRef.current = requestAnimationFrame(animate);} else {cancelAnimationFrame(requestIdRef.current);}};document.addEventListener('visibilitychange', handleVisibilityChange);return () => {cancelAnimationFrame(requestIdRef.current);document.removeEventListener('visibilitychange', handleVisibilityChange);};}, []);return <div>Count: {count}</div>;
};

总结

  • 浏览器优化: 现代浏览器会对不可见页面的定时器进行优化。使用 document.visibilityStatevisibilitychange 事件可以帮助你处理这些情况。
  • requestAnimationFrame: 对于动画任务,requestAnimationFrame 是更合适的选择,但也会受到浏览器优化的影响。

这些实践可以帮助你在前端项目中处理定时器,确保它们在页面可见和不可见状态下的行为符合预期。

那假如我们希望在后台仍然执行定时任务呢?

  • stackoverflow - How can I make setInterval also work when a tab is inactive in Chrome?
  • reddit - setInterval not working in the inactive tab

这里有两个相关话题的讨论,可以阅读和参考。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • RFID固定资产管理系统:提升企业资产管理效率的新利器
  • mybatisplus布隆过滤器解决缓存穿透
  • <数据集>棉花识别数据集<目标检测>
  • FPGA:我的零基础学习路线(2022秋招已上岸)持续更新中~
  • asp.net mvc 三层架构开发商城系统需要前台页面代完善
  • 3千米以上音视频键鼠延长解决方案:KVM光纤延长器
  • 什么是Java并发中的锁池?
  • Redis学习[5] ——Redis过期删除和内存淘汰
  • 使用 ModelScope 本地部署图片变视频模型
  • 深入理解Java注解
  • [网鼎杯 2020 青龙组]AreUSerialz1
  • vue后台管理系统 vue3+vite+pinia+elementui+axios下
  • 接口测试框架中测试用例管理模块的优化与思考!
  • 理解ThreadLocal 变量副本,为什么不同线程的 ThreadLocalMap互不干扰
  • LSTM与GNN强强结合!全新架构带来10倍推理速度提升
  • 分享一款快速APP功能测试工具
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • android 一些 utils
  • C++类中的特殊成员函数
  • CSS 三角实现
  • CSS 提示工具(Tooltip)
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • Kibana配置logstash,报表一体化
  • Nacos系列:Nacos的Java SDK使用
  • PHP变量
  • react-native 安卓真机环境搭建
  • React系列之 Redux 架构模式
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • Stream流与Lambda表达式(三) 静态工厂类Collectors
  • Sublime text 3 3103 注册码
  • 程序员最讨厌的9句话,你可有补充?
  • 经典排序算法及其 Java 实现
  • 警报:线上事故之CountDownLatch的威力
  • 译有关态射的一切
  • 积累各种好的链接
  • #1014 : Trie树
  • #微信小程序:微信小程序常见的配置传旨
  • %check_box% in rails :coditions={:has_many , :through}
  • (55)MOS管专题--->(10)MOS管的封装
  • (CPU/GPU)粒子继承贴图颜色发射
  • (剑指Offer)面试题34:丑数
  • (转载)虚函数剖析
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET8使用VS2022打包Docker镜像
  • .NET开发人员必知的八个网站
  • .NET中分布式服务
  • .net专家(张羿专栏)
  • .vue文件怎么使用_我在项目中是这样配置Vue的
  • @RequestParam,@RequestBody和@PathVariable 区别
  • @RestControllerAdvice异常统一处理类失效原因
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [Algorithm][综合训练][拜访][买卖股票的最好时机(四)]详细讲解
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作