资讯专栏INFORMATION COLUMN

演示当定时器在页面最小化时无法执行

3403771864 / 463人阅读

  我们讲述的是关于 ahooks 源码系列文章的第七篇,总结主要讲述下面几点:

  巩固 React hooks 的理解。

  学习如何抽象自定义 hooks。构建属于自己的 React hooks 工具库。

  培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择。

  注:本系列对 ahooks 的源码解析是基于v3.3.13。自己 folk 了一份源码,主要是对源码做了一些解读,可见详情。

  现在进入主题:定时器。

  useInterval 和 useTimeout,它们的功能对应的是 setInterval 和 setTimeout,那对比后者有什么优势?

  先看useInterval,代码如下所示:

  function useInterval(
  fn: () => void,
  delay: number | undefined,
  options?: {
  immediate?: boolean;
  },
  ) {
  const immediate = options?.immediate;
  const fnRef = useLatest(fn);
  useEffect(() => {
  // 忽略部分代码...
  // 立即执行
  if (immediate) {
  fnRef.current();
  }
  const timer = setInterval(() => {
  fnRef.current();
  }, delay);
  // 清除定时器
  return () => {
  clearInterval(timer);
  };
  // 动态修改 delay 以实现定时器间隔变化与暂停。
  }, [delay]);
  }

  跟 setInterval 的区别如下:

  可以支持第三个参数,通过 immediate 能够立即执行我们的定时器。这样就可以在变更 delay 的时候,会自动清除旧的定时器,并同时启动新的定时器。

  通过 useEffect 的返回清除机制,开发者不需要关注清除定时器的逻辑,避免内存泄露问题。记住这个点是在开发中很容易忽略的。

  useTimeout 跟上面很类似,如下所示,不再做额外解释:

  function useTimeout(fn: () => void, delay: number | undefined): void {
  const fnRef = useLatest(fn);
  useEffect(() => {
  // ...忽略部分代码
  const timer = setTimeout(() => {
  fnRef.current();
  }, delay);
  return () => {
  clearTimeout(timer);
  };
  // 动态修改 delay 以实现定时器间隔变化与暂停。
  }, [delay]);
  }

  setTimeout 和 setInterval 的问题

  首先,setTimeout 和 setInterval 作为事件循环中宏任务的“两大主力”,要知道它的执行时机无法和我们预期一样准确的,它要做的就是等待,在前面任务的执行。比如下面的 setTimeout 的第二个参数设置为 0,并不会立即执行。

  setTimeout(() => {
  console.log('test');
  }, 0)

  另外还有一种情况,setTimeout 和 setInterval 在浏览器不可见的时候(比如最小化的时候),不同的浏览器中设置不同的时间间隔的时候,得出的表现就不一样。根据当浏览器切换到其他标签页或者最小化时,你的js定时器还准时吗?这篇文章的实践结论如下:

  当谷歌浏览器中,在页面处于不可见状态时,setInterval 的最小间隔时间会被限制为 1s。可在火狐浏览器的 setInterval 和谷歌特性一致,但是 ie 浏览器没有对不可见状态时的 setInterval 进行性能优化,不可见前后间隔时间不变。

  在谷歌浏览器中,setTimeout在浏览器不可见状态下间隔低于1s的会变为1s,大于等于1s的会变成N+1s的间隔值。火狐浏览器下setTimeout的最小间隔时间会变为1s,大于等于1s的间隔不变。ie浏览器在不可见状态前后的间隔时间不变。

  总结上面看来,差异不小,就要换个,那就是 requestAnimationFrame。

  window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

  为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe>里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命

  所以,ahooks 也提供了使用requestAnimationFrame进行模拟定时器处理的 hook,我们一起来看下。

  useRafInterval 和 useRafTimeout

  直接看useRafInterval。(useRafTimeout 和 useRafInterval 类似,这里不展开细说)。

  function useRafInterval(
  fn: () => void,
  delay: number | undefined,
  options?: {
  immediate?: boolean;
  },
  ) {
  const immediate = options?.immediate;
  const fnRef = useLatest(fn);
  useEffect(() => {
  // 省略部分代码...
  const timer = setRafInterval(() => {
  fnRef.current();
  }, delay);
  return () => {
  clearRafInterval(timer);
  };
  }, [delay]);
  }

  上述代码中展示出,跟前面的 useInterval 大部分代码逻辑都是一样的,但不同的就是定时使用了setRafInterval方法,清除定时器用了clearRafInterval。

  setRafInterval

  直接上代码:

  const setRafInterval = function (callback: () => void, delay: number = 0): Handle {
  if (typeof requestAnimationFrame === typeof undefined) {
  // 如果不支持,还是使用 setInterval
  return {
  id: setInterval(callback, delay),
  };
  }
  // 开始时间
  let start = new Date().getTime();
  const handle: Handle = {
  id: 0,
  };
  const loop = () => {
  const current = new Date().getTime();
  // 当前时间 - 开始时间,大于设置的间隔,则执行,并重置开始时间
  if (current - start >= delay) {
  callback();
  start = new Date().getTime();
  }
  handle.id = requestAnimationFrame(loop);
  };
  handle.id = requestAnimationFrame(loop);
  return handle;
  };

  首先是用 typeof 判断进行兼容逻辑处理,出现兼容,那就需要setInterval出场。

  初始记录一个 start 的时间。

  在 requestAnimationFrame 回调中,判断现在的时间减去开始时间有没有达到间隔,假如达到则执行我们的 callback 函数。更新开始时间。

  clearRafInterval

  清除定时器。

  function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer {
  return typeof cancelAnimationFrame === typeof undefined;
  }
  // 清除定时器
  const clearRafInterval = function (handle: Handle) {
  if (cancelAnimationFrameIsNotDefined(handle.id)) {
  return clearInterval(handle.id);
  }
  cancelAnimationFrame(handle.id);
  };

  假如不支持cancelAnimationFrameAPI,则通过 clearInterval 清除,支持则直接使用 cancelAnimationFrame 清除。

  复盘与总结

  我们开发者对于定时器,要记得及时清楚,这里建议用seEffect返回清除副作用函数这个特性,我们可以将这类逻辑一起封装到 hook 中,怎么样是不是很方便。

  还有就是在希望在页面不可见的时候,不执行定时器,可以选择 useRafInterval 和 useRafTimeout,其内部是使用requestAnimationFrame进行实现。

  有关定时器在页面最小化时不执行实现示例的详细内容已叙述完,请大家多多关注后续更多精彩内容。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/128190.html

相关文章

  • 《高性能javascript》阅读摘要

    摘要:当执行上下文被创建时,它的作用域链初始化为当前运行函数的属性中的对象。该过程搜索执行环境的作用域链,查找同名的标识符。搜索实例成员比从字面量或局部变量中读取数据代价更高,再加上遍历原型链带来的开销,这让性能问题更为严重。 最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践、性能优化类的书。记录下主要知识。 加载和执行 脚本位置 放在中的...

    duan199226 评论0 收藏0
  • 《高性能javascript》阅读摘要

    摘要:当执行上下文被创建时,它的作用域链初始化为当前运行函数的属性中的对象。该过程搜索执行环境的作用域链,查找同名的标识符。搜索实例成员比从字面量或局部变量中读取数据代价更高,再加上遍历原型链带来的开销,这让性能问题更为严重。 最近在阅读这本Nicholas C.Zakas(javascript高级程序设计作者)写的最佳实践、性能优化类的书。记录下主要知识。 加载和执行 脚本位置 放在中的...

    afishhhhh 评论0 收藏0
  • 浏览器知识

    摘要:浏览器的渲染进程是多线程的。异步请求线程在在连接后是通过浏览器新开一个线程请求将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中。 [TOC] 浏览器进程线程 区分线程和进程 **- 什么是进程** 狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being exe...

    Pluser 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<