web如何检测版本更新(二)

前言

上篇文章提到方案一有个弊端,获取hash值方式的扩展性不高,要按不同项目的实际情况去取。而且如果项目其他成员不知道这个逻辑并且在打包后在你取值的前面去插入一个script标签,那么就是一首凉凉,这时候用户就会不停的收到版本更新的提示。

思路

  • 打包完后自动执行个脚本在head标签插入一个meta标签来记录发版版本号。
  • 检测当前版本号与最新版本号是否一致,若不一致则提示用户有新版本更新。

实现

1.创建脚本

  • 在项目根节点创建一个version.js的文件,代码如下
const fs = require('fs');

/**版本号,当前时间戳,保证唯一 */
const versionNo = new Date().getTime();
const htmlPath = './build/index.html';
const insertVersionMeta = () => {
  let html = fs.readFileSync(htmlPath, 'utf-8');
  html = html.replace('<head>', `<head><meta name="version_no" content="${versionNo}" />`);
  fs.writeFileSync(htmlPath, html);
};
insertVersionMeta();
复制代码
  1. 执行脚本
  • 在package.json加个自定义命令执行node脚本,并且在build完成后自动执行
{
    ...(省略其他配置)
    "build": "cross-env NODE_ENV=production react-app-rewired build && npm run version",
    "version": "node ./version.js"
    ...(省略其他配置)
 }
复制代码

这样每次打包完后就会自动往html里面插入版本信息了。

3.检测当前版本号与最新版本号是否一致(其实这里的逻辑与上一篇的大同小异)

  • 获取当前版本号
  const getCurrentVersion = useCallback(() => {
    let version = '';
    const metaList = document.querySelectorAll('meta');
    if (metaList.length) {
      metaList.forEach((item) => {
        if (item.name === 'version_no') {
          version = item.content;
        }
      });
    }
    curVersion.current = version;
  }, []);
复制代码
  • 获取最新版本号
 /*** timestamp-时间戳防止请求缓存*/
 const fetchNewVersion = useCallback(async () => {
    // 在 js 中请求首页地址不会更新页面
    const timestamp = new Date().getTime();
    const response = await axios.get(`${window.location.origin}?time=${timestamp}`);
    // 返回的是字符串,需要转换为 html
    const el = document.createElement('html');
    el.innerHTML = response.data;
    let newVersion = '';
    // 拿到版本号
    const metaList = el.querySelectorAll('meta');
    if (metaList.length) {
      metaList.forEach((item) => {
        if (item.name === 'version_no') {
          newVersion = item.content;
        }
      });
    }
    console.log('fetchNewVersion', curVersion.current, newVersion);
    if (newVersion && newVersion !== curVersion.current && !visible) {
      // 版本更新,弹出提示
      setVisible(true);
    } else if (newVersion && newVersion === curVersion.current && visible) {
      setVisible(false);
    }
  }, [visible]);
复制代码

接下来就是触发时机,我的想法是将其封装成组件然后挂载在项目的跟节点下,初始化项目的时候,触发一次更新,并且开启轮询检测(半小时/一小时一次)如果用户离开或者熄屏则清除轮询。点亮屏幕或切回网页所在的页签则触发检测,并且开启轮询。

4.提示用户更新

在用户点击刷新后,自动刷新,浏览器,并关闭提示。

image.png

  /** 立即刷新 */
  const handleConfirm = useCallback(() => {
    setVisible(false);
    window.location.reload();
  }, []);

复制代码

写在最后

该方案与方案一相比,可扩展性较高,不需要较多的关注html的结构,个人比较推荐这个方案。其实开发的过程,难的是好的方案,方案一有其实开发也就成功了一半。

  • 完整代码
import React, { useState, useRef, useEffect, useCallback, useMemo, memo } from 'react';
import axios from 'axios';

import LionetConfirm from '@/shared/components/LionetConfirm';
import { setTrackerUserId } from './Tracker';

/** 检测版本更新 */
const VersionUpdateToast = () => {
  /** 当前版本号 */
  const curVersion = useRef<any>(null);
  /** 定时器 */
  const timer = useRef<any>(null);
  const [visible, setVisible] = useState(false);

  /** 轮询间隔 */
  const pollingTime = useMemo(() => {
    return 30 * 60 * 1000;
  }, []);

  /** 获取最新版本号
   * timestamp-时间戳防止请求缓存
   */
  const fetchNewVersion = useCallback(async () => {
    // 在 js 中请求首页地址不会更新页面
    const timestamp = new Date().getTime();
    const response = await axios.get(`${window.location.origin}?time=${timestamp}`);
    // 返回的是字符串,需要转换为 html
    const el = document.createElement('html');
    el.innerHTML = response.data;
    let newVersion = '';
    // 拿到 版本号
    const metaList = el.querySelectorAll('meta');
    if (metaList.length) {
      metaList.forEach((item) => {
        if (item.name === 'version_no') {
          newVersion = item.content;
        }
      });
    }
    console.log('fetchNewVersion', curVersion.current, newVersion);
    if (newVersion && newVersion !== curVersion.current && !visible) {
      // 版本更新,弹出提示
      setVisible(true);
    } else if (newVersion && newVersion === curVersion.current && visible) {
      setVisible(false);
    }
  }, [visible]);

  /** 开启定时器 */
  const setTimer = useCallback(() => {
    timer.current = setInterval(fetchNewVersion, pollingTime);
  }, [fetchNewVersion, pollingTime]);

  /** 清除定时器 */
  const clearTimer = useCallback(() => {
    if (timer.current) {
      clearInterval(timer.current);
      timer.current = null;
    }
  }, []);

  /** 获取当前版本号 */
  const getCurrentVersion = useCallback(() => {
    let version = '';
    const metaList = document.querySelectorAll('meta');
    if (metaList.length) {
      metaList.forEach((item) => {
        if (item.name === 'version_no') {
          version = item.content;
        }
      });
    }
    curVersion.current = version;
  }, []);

  /** 获取当前版本号 */
  const getVersion = useCallback(() => {
    getCurrentVersion();
    fetchNewVersion();
  }, [fetchNewVersion, getCurrentVersion]);

  /** 浏览器窗口是否显示隐藏 */
  const onVisibilityChange = useCallback(() => {
    // eslint-disable-next-line prefer-destructuring
    if (!document.hidden) {
      setTimer();
      getVersion();
    } else {
      clearTimer();
    }
  }, [clearTimer, getVersion, setTimer]);

  useEffect(() => {
    getVersion();
    setTimer();
    return () => {
      clearTimer();
    };
  }, []);

  useEffect(() => {
    document.addEventListener('visibilitychange', onVisibilityChange);
    return () => {
      document.removeEventListener('visibilitychange', onVisibilityChange);
    };
  }, []);

  /** 立即刷新 */
  const handleConfirm = useCallback(() => {
    setVisible(false);
    setTrackerUserId();
    window.location.reload();
  }, []);

  return (
    <LionetConfirm
      visible={visible}
      isShowCancelBtn={false}
      confirmText="立即刷新"
      onConfirm={handleConfirm}
    >
      <span>发现新版本,请刷新后使用哦~</span>
    </LionetConfirm>
  );
};

export default memo(VersionUpdateToast);

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享