实战分享:20分钟页面不操作,页面失效

点击上方 前端Q,关注公众号

回复加群,加入前端Q技术交流群

作者:吃腻的奶油

https://juejin.cn/post/7340636105765535796

场景需求

05abd60dacaa3ad500f02dec85e7c0eb.jpeg

总结:

  1. 20分钟内如果不操作,页面就是提示失效并且回到列表页面,如果操作了,计时就会清零。

  2. 如果 A  在编辑,B 点击编辑会提示正在编辑。A 在编辑期间,每分钟会向后端发送续租(即正在编辑)的请求,后端收到请求后,会在服务端帮你保留这一分钟的编辑状态,别人就无法在编辑了。并且别人编辑时,后端会返回相应的信息。

前言

乐了,产品提出了需求,然后我去找导师问问团队中有没有现成的解决方案。。。没有,然后导师提出了 web worker 的思路,让我自己思考解决方案。好吧,那就开始吧。

一开始,我想着能否能用 setInterval 来进行定时的,结果后端发来消息

985f639550c3efc9ba9a2fcba2a6c303.jpeg

emm......后端大佬,惹不起~

如图上所说,如果切换了页面,setInterval 会停止计时的(咱就说不信的可以试试),也就是说这个线程被停止了。

那么就需要新建一个线程,也就是 web worker 了,用它单纯来进行计时,不用管其他逻辑,切换页面也不会终止。

正文思路

基本demo

首先,百度了下 web worker 的基本实现案例,一文彻底学会使用web worker

需要该需求的页面

// editEmail.vue(主线程)
<template>
    <div>编辑页面哦</div>
</template>

<script>
const myWorker = new Worker('/worker.js'); // 创建worker,函数里的值为 webWorker 文件名

 // 向 worker.js 线程发送消息,对应 worker.js 线程中的 e.data
myWorker.postMessage('Greeting from Main.js');

myWorker.addEventListener('message', e => { // 从 worker.js 那接收消息
    console.log(e.data); // Greeting from Worker.js,worker线程发送的消息
});

</script>

放入 public 文件下 worker.js

// worker.js(worker线程)
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    self.postMessage('Greeting from Worker.js'); // 向主线程发送消息
});

其中,worker.js 的存放路径和 new Worker()里的值有关,比如此时我是在本地资源的根路径创建的 /worker.js ,那么就是放在public下的。

而如果是 ./worker.js,或者 ../worker.js,这是无法找到的,因为此时的 worker.js 已经被打包编译成了 app.js。

注意,public 文件的变动需要重启项目,和 vue.config.js一样

d25f46589144e7df4155f365618919e7.jpeg worker.js 和 主线程通信走通后,开始分析需求了。

1. 每分钟续租一次 =》 1秒钟续租一次

什么叫续租,每分钟你向服务端发送一个续租请求,后端就会帮你保持正在编辑的状态(假设为 edit: true),而且后端其实也在计时一分钟。在这一分钟内,由于 edit 为 true,如果别人想要编辑,就会拒绝别人的编辑。如果你一分钟后没发送这个续租请求,后端会把 true 改成 false,这时别人想要编辑,后端就会接受别人的编辑了。

因此,前端就需要每隔一分钟发送一次续租请求,来维持此时的编辑状态。

当然,由于产品要求的更复杂,你发送续租请求的时候请求头往往会携带用户信息,来反馈谁在进行编辑以提高用户体验感。

下述代码为了更好的测试,把每分钟续租变为了每秒续租一次

2. 20分钟期间不操作就会提示页面失效 =》 10秒钟一到就会触发提示事件

当然,就算 setInterval 不能作为解决方案,但还是需要用它来做定时器的,这还是挺香的。

// worker.js(worker线程)
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    setInterval(() => {
        self.postMessage('Greeting from Worker.js'); // 向主线程发送消息
    }, 1 * 1000)
});

如上代码,Greeting from Worker.js 这条消息每隔 1 秒钟就会向 editEmail.vue 页面发送,这时就算你切换浏览器标签页也仍然会发送。

好,简单的定时器做完了,那就开始进行计时了。

// worker.js(worker线程)
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    
    let sum = 0;
    let msg;
    
    setInterval(() => {
        sum += 1;
        msg = {
            text: 'editing',
            sum
        }
        self.postMessage(msg); // 向主线程发送消息 msg 对象
    }, 1 * 1000)
});

每过一秒,worker.js 都会发送一次信息,用来持续触发续租事件,而 sum 则是用来进行计时过了多少秒。

fb3a4fe5e46b5b1169a73762ec740842.jpeg
// editEmail.vue(主线程)
<template>
    <div>编辑页面哦</div>
</template>

<script>
const myWorker = new Worker('/worker.js'); // 创建worker,函数里的值为 webWorker 文件名

 // 向 worker.js 线程发送消息,对应 worker.js 线程中的 e.data
myWorker.postMessage('start');

myWorker.addEventListener('message', e => { // 从 worker.js 那接收消息,每隔一秒都会接收到
    console.log(e.data); // {text: 'editing', sum: sum},worker线程发送的消息
    campaignListLock(); // 发起续租请求
    if (e.data.sum >= 10) {
        message.error("页面失效");
        PageGoBack(); // 返回上一页,看产品要求
    }
});

</script>
6e80661d7e22d57ed52f92209fe8f193.jpeg

OK,这样,基本的需求就完成了,10 秒一到就会提示页面失效,并且在这 10 秒内谁都无法进入编辑页面(在进入编辑页面前得先向后端请求看看是否有人在编辑)。

但是,10 秒后呢,这个计时器仍然在进行中,所以我需要在 10 秒过后清除这个计时器了。也就是在 e.data.sum >= 10 这个条件内对 worker 进程进行通信,触发清除事件。

// editEmail.vue(主线程)
<template>
     <div>编辑页面哦</div>
     <input @change="onChange" />
</template>

<script>
const myWorker = new Worker('/worker.js'); // 创建worker,函数里的值为 webWorker 文件名

 // 向 worker.js 线程发送消息,对应 worker.js 线程中的 e.data
myWorker.postMessage('start');

myWorker.addEventListener('message', e => { // 从 worker.js 那接收消息,每隔一秒都会接收到
    console.log(e.data); // {text: 'editing', sum: sum},worker线程发送的消息
    campaignListLock(); // 发起续租请求
    if (e.data.sum >= 10) {
        message.error("页面失效");
        PageGoBack(); // 返回上一页,看产品要求
        myWorker.postMessage('end');
    }
});

</script>

在这里我们分别向 worker 进程发送了 startend 两个信息,worker 进程拿到信息后进行判断,如果是 start,那么就开始每秒续租,如果为 end,那么就清除定时器来终止续租(即停止每秒向主线程进行通信来触发续租请求)。

// worker.js(worker线程)
let timer;
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    
    let sum = 0;
    let msg;
    
    if (e.data === "start") {
         timer = setInterval(() => {
            sum += 1;
            msg = {
                text: '编辑中',
                sum
            }
            self.postMessage(msg); // 向主线程发送消息 msg 对象
         }, 1 * 1000)
    } else {
        clearInterval(timer);
    }
});

如上代码,定义一个全局变量 timer 用来存储定时器,以便能够随时清除。

cb9d8ea1121e671f730933056dcd6ad8.jpeg

定时重置

Stop,别冲太猛,这里我们需要总结一下了

开启定时

myWorker.postMessage('start');

就会重新 worker.js 中的 self.addEventListener('message',()=>{}) 函数,sum 重置为 0,计时重新开始计算。

停止定时

myWorker.postMessage('end');

就会触发 worker 中的 clearInterval(timer) 来清除定时器

重置定时

myWorker.postMessage('end');
myWorker.postMessage('start');

先清除定时器停止定时,然后再重新开启定时

最后

// 开启定时
const onTimeStart = () => {
    myWorker.postMessage('start');
}
// 停止定时
const onTimeEnd = () => {
    myWorker.postMessage('end');
}
// 重置定时
const onTime = () => {
    onTimeEnd();
    onTimeStart();
}

3. 10 秒内如果进行了表单操作则重置计时

const onChange = () => {
    onTime();
}

优化代码

// editEmail.vue(主线程)
<template>
    <div>编辑页面哦</div>
    <input @change="onChange" />
</template>

<script>
const myWorker = new Worker('/worker.js'); // 创建worker,函数里的值为 webWorker 文件名

 // 向 worker.js 线程发送消息,对应 worker.js 线程中的 e.data
myWorker.postMessage('start');

// 开启定时
const onTimeStart = () => {
    myWorker.postMessage('start');
}
// 停止定时
const onTimeEnd = () => {
    myWorker.postMessage('end');
}
// 重置定时
const onTime = () => {
    onTimeEnd();
    onTimeStart();
}

myWorker.addEventListener('message', e => { // 从 worker.js 那接收消息,每隔一秒都会接收到
    console.log(e.data); // {text: 'editing', sum: sum},worker线程发送的消息
    campaignListLock(); // 发起续租请求
    if (e.data.sum >= 10) {
        message.error("页面失效");
        PageGoBack(); // 返回上一页,看产品要求
        onTimeEnd(); // 停止计时,终止续租
    }
});

const onChange = () => {
    onTime();
}

</script>
// worker.js(worker线程)
let timer;
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    
    let sum = 0;
    let msg;
    
    if (e.data === "start") {
         timer = setInterval(() => {
            sum += 1;
            msg = {
                text: 'editing',
                sum
            }
            self.postMessage(msg); // 向主线程发送消息 msg 对象
         }, 1 * 1000)
    } else {
        clearInterval(timer);
    }
});

而到这里,只是实现了单纯的停留在页面,但切换浏览器标签页时,没有做相应的监听事件。虽然有着另一个 worker 线程在运行着,但当你切换页面后过 10s 再返回原页面,提示虽然会有,但是一闪即逝,基本看不到提示信息。

4. 切换浏览器标签页

而监听浏览器标签页的切换事件是 visibilitychangedocument.visibilityStat 属性

document.addEventListener("visibilitychange", function () {
  if (document.visibilityState == "visible") {
    message.error("页面已失效");
  } else if  (document.visibilityState == "hidden") {
      message.error("页面已隐藏");
  }
});

其中的隐藏我们并不需要用到,而且过了 10s 后如果反复的切换标签,“页面已失效”的提示会反复的弹出,因为我们并没有进行控制。

此时我们也需要区分过了 10s 后用户是停留在当前页面还是离开了页面又返回了。

如果是停留,那么页面属性为 visible。如果是返回,那么就需要监听 visibilitychange 事件并且页面属性为 visible

let timeCount = 0; // 全局中定义变量,用以控制切换标签页后的提示次数。

myWorker.addEventListener("message", (e) => {
    if (e.data.notime >= 10) {
       onTimingEnd();
       if (document.visibilityState === "visible") {
          message.error("页面已失效");
          pageGoBack();
          timeCount = 1; // 触发了提示就禁止它后续再触发
       }
       document.addEventListener("visibilitychange", function () {
          if (document.visibilityState == "visible" && timeCount == 0) {
            message.error("页面已失效");
            pageGoBack();
            timeCount = 1; // 触发了提示就禁止它后续再触发
          }
        });
    }
})

最终代码

替换成20分钟了

// editEmail.vue(主线程)
<template>
    <div>编辑页面哦</div>
    <input @change="onChange" />
</template>

<script>
const myWorker = new Worker('/worker.js'); // 创建worker,函数里的值为 webWorker 文件名

 // 向 worker.js 线程发送消息,对应 worker.js 线程中的 e.data
myWorker.postMessage('start');

// 开启定时
const onTimeStart = () => {
    myWorker.postMessage('start');
}
// 停止定时
const onTimeEnd = () => {
    myWorker.postMessage('end');
}
// 重置定时
const onTime = () => {
    onTimeEnd();
    onTimeStart();
}

myWorker.addEventListener('message', e => { // 从 worker.js 那接收消息,每隔一秒都会接收到
    console.log(e.data); // {text: 'editing', sum: sum},worker线程发送的消息
    campaignListLock(); // 发起续租请求
    if (e.data.sum >= 20) { // 超过 20 分钟,终止续租并提示页面失效
        onTimingEnd();
        if (document.visibilityState === "visible") {
          message.error("页面已失效");
          pageGoBack();
          timeCount = 1; // 触发了提示就禁止它后续再触发
        }
        document.addEventListener("visibilitychange", function () {
          if (document.visibilityState == "visible" && timeCount == 0) {
            message.error("页面已失效");
            pageGoBack();
            timeCount = 1; // 触发了提示就禁止它后续再触发
          }
        });
    }
});

const onChange = () => {
    onTime();
}

</script>
// worker.js(worker线程)
let timer;
self.addEventListener('message', e => { // 接收到消息
    console.log(e.data); // Greeting from Main.js,主线程发送的消息
    
    let sum = 0;
    let msg;
    
    if (e.data === "start") {
         timer = setInterval(() => {
            sum += 1;
            msg = {
                text: 'editing',
                sum
            }
            self.postMessage(msg); // 向主线程发送消息 msg 对象
         }, 60 * 1000) // 每分钟 sum 加 1 标识积累了 1 分钟
    } else {
        clearInterval(timer);
    }
});

f18dabe5e3d4b3cbdfc68369df070797.png

往期推荐

Vue 存储插件的底层原理,你不知道的 localStorage API

05aee0da704e0ee1de46e2d622b82f97.png

苦等三年,React Compiler 终于能用了。使用体验:很爽,但仍有瑕疵

86015be8f9b8ffc964db226df30f5356.png

Vite 为何短短几年内变成这样?

6a35bb65591f18954dec9338ca4d2a53.png


最后

  • 欢迎加我微信,拉你进技术群,长期交流学习...

  • 欢迎关注「前端Q」,认真学前端,做个专业的技术人...

b4392c20892576ceae93e8b929be6efc.jpeg

ef827107a9c0f8ae2474d18685ebe13b.png

点个在看支持我吧

b2644b65717cce3c70ab48d06b7f3717.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值