以下内容来自公众号逆锋起笔,关注每日干货及时送达
作者:bigo前端
https://juejin.cn/post/7038405108371030047
一、前言
计时器为什么要用setTimeout而不用setInterval 为什么不直接将剩余时间-1。 如何将所需要的时间返回出去(有可能我只需要分钟和秒数,那就只返回分钟和秒数,也有可能我全都要)。 不确定接口返回的是剩余时间还是截止日期,该怎么同时兼容这两种情况。 不确定接口返回的时间是秒还是毫秒单位。
二、开始手操 前端开发博客
1. 先创建一个vue组件
<template>
<div class="_base-count-down">
</div>
</template>
<script>
export default {
data: () => ({
}),
props: {
},
};
</script>
<style lang='scss' scoped>
</style>
2. 实现基本的倒计时组件
time
传入这个倒计时组件,由于time可能是秒为单位的,也有可能是毫秒为单位的,所以我们需要在传入time
的是有也传入一个isMilliSecond
来告诉倒计时组件这个time
是毫秒还是秒为单位的。如下代码中的props
所示。<template>
<div class="_base-count-down">
</div>
</template>
<script>
export default {
data: () => ({
}),
props: {
time: {
type: [Number, String],
default: 0
},
isMilliSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
};
</script>
<style lang='scss' scoped>
</style>
computed
中的duration是将time进行转化的结果,不管time
是毫秒还是秒,都转化为秒 不知道你注意到了没有:+this.time
。为什么要在前面加个‘ + ’号。这点很值得我们学习,因为接口返回的一串数字有时候是字符串的形式,有时候是数字的形式(不能过分相信后端同学,必须自己做好防范)。所以通过前面加个‘ + ’号 通通转化为数字。现在的duration
就是转化后的time
啦!<template>
<div class="_base-count-down">
</div>
</template>
<script>
export default {
data: () => ({
}),
props: {
time: {
type: [Number, String],
default: 0
},
isMilliSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
// 新增代码:
mounted() {
this.countDown();
},
methods: {
countDown() {
this.getTime(this.duration);
},
}
};
</script>
<style lang='scss' scoped>
</style>
countDown
方法调用了getTime方法,getTime需要传入duration这个参数,也就是我们获得的剩余时间。<template>
<div class="_base-count-down">
还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
</div>
</template>
<script>
export default {
data: () => ({
days: '0',
hours: '00',
mins: '00',
seconds: '00',
timer: null,
}),
props: {
time: {
type: [Number, String],
default: 0
},
isMilliSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
mounted() {
this.countDown();
},
methods: {
countDown() {
this.getTime(this.duration);
},
// 新增代码:
getTime(duration) {
this.timer && clearTimeout(this.timer);
if (duration < 0) {
return;
}
const { dd, hh, mm, ss } = this.durationFormatter(duration);
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
this.timer = setTimeout(() => {
this.getTime(duration - 1);
}, 1000);
}
}
};
</script>
<style lang='scss' scoped>
</style>
durationFormatter
是一个将duration
转化成天数,小时,分钟,秒数的方法,很简单,可以看下它的具体实现。durationFormatter(time) {
if (!time) return { ss: 0 };
let t = time;
const ss = t % 60;
t = (t - ss) / 60;
if (t < 1) return { ss };
const mm = t % 60;
t = (t - mm) / 60;
if (t < 1) return { mm, ss };
const hh = t % 24;
t = (t - hh) / 24;
if (t < 1) return { hh, mm, ss };
const dd = t;
return { dd, hh, mm, ss };
},
3. 为什么要用setTimeout来模拟setInterval的行为?
setTimeout(function(){··· }, n); // n毫秒后执行function
setInterval(function(){··· }, n); // 每隔n毫秒执行一次function
再次强调,定时器指定的时间间隔,表示的是何时将定时器的代码添加到消息队列,而不是何时执行代码。所以真正何时执行代码的时间是不能保证的,取决于何时被主线程的事件循环取到,并执行。
setInterval(function, N)
//即:每隔N秒把function事件推到消息队列中
使用setInterval时,某些间隔会被跳过; 可能多个定时器会连续执行;
4. 为什么要clearTimeout(this.timer)
this.timer && clearTimeout(this.timer);
这一句?this.countDown();
只会执行一次,也就是说this.getTime(this.duration);
只会执行一次,因此duration还是活动一的时间,怎么办呢?watch派上用场了。微信搜索readdot,关注后回复 视频教程 获取23种精品资料。<template>
<div class="_base-count-down">
还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
</div>
</template>
<script>
export default {
data: () => ({
days: '0',
hours: '00',
mins: '00',
seconds: '00',
timer: null,
}),
props: {
time: {
type: [Number, String],
default: 0
},
isMilliSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
mounted() {
this.countDown();
},
// 新增代码:
watch: {
duration() {
this.countDown();
}
},
methods: {
countDown() {
this.getTime(this.duration);
},
durationFormatter(){...}
getTime(duration) {
this.timer && clearTimeout(this.timer);
if (duration < 0) {
return;
}
const { dd, hh, mm, ss } = this.durationFormatter(duration);
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
this.timer = setTimeout(() => {
this.getTime(duration - 1);
}, 1000);
}
}
};
</script>
<style lang='scss' scoped>
</style>
this.timer && clearTimeout(this.timer);
这一句?countDown()
,也就调用this.getTime(this.duration);
,然后执行到setTimeout,也会一秒后把回调函数放到任务队列中。this.timer && clearTimeout(this.timer);
这一句的原因了。就是要把上一个setTimeout清除掉。5. 使用 diffTime
还剩1天12:25:25
,然后有人给你发微信,你马上切换到微信,回复消息后切回浏览器,发现倒计时时间却还是还剩1天12:25:25
。你慌了:你写的代码出现bug了!<template>
<div class="_base-count-down">
还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
</div>
</template>
<script>
export default {
data: () => ({
days: '0',
hours: '00',
mins: '00',
seconds: '00',
timer: null,
curTime: 0,// 新增代码:
}),
props: {
time: {
type: [Number, String],
default: 0
},
isMilliSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
mounted() {
this.countDown();
},
watch: {
duration() {
this.countDown();
}
},
methods: {
countDown() {
// 新增代码:
this.curTime = Date.now();
this.getTime(this.duration);
},
durationFormatter(){...}
getTime(duration) {
this.timer && clearTimeout(this.timer);
if (duration < 0) {
return;
}
const { dd, hh, mm, ss } = this.durationFormatter(duration);
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
this.timer = setTimeout(() => {
// 新增代码:
const now = Date.now();
const diffTime = Math.floor((now - this.curTime) / 1000);
this.curTime = now;
this.getTime(duration - diffTime);
}, 1000);
}
}
};
</script>
<style lang='scss' scoped>
</style>
curTime
赋值Date.now()
,也就是当前的时刻,也就是显示在页面上的那个时刻。-1
改成了-diffTime
。this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
this.curTime = Date.now();
就记录下了此刻的时间点。const now = Date.now();
记录当前这个setTimeout的回调函数执行的时间点。const diffTime = Math.floor((now - this.curTime) / 1000);
记录当前这个setTimeout的回调函数执行的时间点距离页面上开始 渲染 剩余时间的 这一段时间。其实此时的diffTime就是=1。this.curTime = now;
将curTime的值变成当前这个setTimeout的回调函数执行的时间点。this.getTime(duration - diffTime);
其实就是this.getTime(duration - 1);
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
const now = Date.now();
记录当前这个setTimeout的回调函数执行的时间点。const diffTime = Math.floor((now - this.curTime) / 1000);
实际上,diffTime的值就是5秒。this.getTime(duration - diffTime);
其实就是this.getTime(duration - 5);
6. 添加新功能:可以传入到期时间。
computed: {
duration() {
if (this.end) {
let end = String(this.end).length >= 13 ? +this.end : +this.end * 1000;
end -= Date.now();
return end;
}
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
7. 添加新功能:可以选择要显示的内容,例如只显示秒,或者只显示小时。
<template>
<div class="_base-count-down no-rtl">
<div class="content">
<slot v-bind="{
d: days, h: hours, m: mins, s: seconds,
hh: `00${hours}`.slice(-2),
mm: `00${mins}`.slice(-2),
ss: `00${seconds}`.slice(-2),
}"></slot>
</div>
</div>
</template>
<base-counter v-slot="timeObj" :time="countDown">
<div class="count-down">
<div class="icon"></div>
{{timeObj.d}}天{{timeObj.hh}}小时{{timeObj.mm}}分钟{{timeObj.ss}}秒
</div>
</base-counter>
00${hours}
.slice(-2) 这种写法也很值得学习。以前在获得到分钟的时候,要手动判断获得的分钟是两位数还是一位数,如果是一位数的话就要在前面手动补上0。就像下面的代码:var StartMinute = startDate.getMinutes().toString().length >= 2 ? startDate.getMinutes() : '0' + startDate.getHours();
00${hours}
.slice(-2) 则不用判断,先补上0再说,然后再从后面往前截取两位。三、学习总结
明白了setInterval的缺点以及用setTimeout代替setInterval。
学到了
“+”
,操作,不管三七二十一,将接口得到的长串数字转化为数字保平安。利用clearTimeout来清除掉之前的计时器,以防止造成影响。
学会使用v-slot来子传父传值
学会一个倒计时组件,为了以后方便cv操作。把组件完整代码贴上:
<template>
<div class="_base-count-down no-rtl">
<div class="content">
<slot v-bind="{
d: days, h: hours, m: mins, s: seconds,
hh: `00${hours}`.slice(-2),
mm: `00${mins}`.slice(-2),
ss: `00${seconds}`.slice(-2),
}"></slot>
</div>
</div>
</template>
<script>
/* eslint-disable object-curly-newline */
export default {
data: () => ({
days: '0',
hours: '00',
mins: '00',
seconds: '00',
timer: null,
curTime: 0
}),
props: {
time: {
type: [Number, String],
default: 0
},
refreshCounter: {
type: [Number, String],
default: 0
},
end: {
type: [Number, String],
default: 0
},
isMiniSecond: {
type: Boolean,
default: false
}
},
computed: {
duration() {
if (this.end) {
let end = String(this.end).length >= 13 ? +this.end : +this.end * 1000;
end -= Date.now();
return end;
}
const time = this.isMiniSecond ? Math.round(+this.time / 1000) : Math.round(+this.time);
return time;
}
},
mounted() {
this.countDown();
},
watch: {
duration() {
this.countDown();
},
refreshCounter() {
this.countDown();
}
},
methods: {
durationFormatter(time) {
if (!time) return { ss: 0 };
let t = time;
const ss = t % 60;
t = (t - ss) / 60;
if (t < 1) return { ss };
const mm = t % 60;
t = (t - mm) / 60;
if (t < 1) return { mm, ss };
const hh = t % 24;
t = (t - hh) / 24;
if (t < 1) return { hh, mm, ss };
const dd = t;
return { dd, hh, mm, ss };
},
countDown() {
// eslint-disable-next-line no-unused-expressions
this.curTime = Date.now();
this.getTime(this.duration);
},
getTime(time) {
// eslint-disable-next-line no-unused-expressions
this.timer && clearTimeout(this.timer);
if (time < 0) {
return;
}
// eslint-disable-next-line object-curly-newline
const { dd, hh, mm, ss } = this.durationFormatter(time);
this.days = dd || 0;
// this.hours = `00${hh || ''}`.slice(-2);
// this.mins = `00${mm || ''}`.slice(-2);
// this.seconds = `00${ss || ''}`.slice(-2);
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
this.timer = setTimeout(() => {
const now = Date.now();
const diffTime = Math.floor((now - this.curTime) / 1000);
const step = diffTime > 1 ? diffTime : 1; // 页面退到后台的时候不会计时,对比时间差,大于1s的重置倒计时
this.curTime = now;
this.getTime(time - step);
}, 1000);
}
}
};
</script>
<style lang='scss' scoped>
@import '~@assets/css/common.scss';
._base-count-down {
color: #fff;
text-align: left;
position: relative;
.content {
width: auto;
display: flex;
align-items: center;
}
span {
display: inline-block;
}
.section {
position: relative;
}
}
</style>
逆锋起笔
专注于程序员圈子,你不但可以学习到java
、python
等主流技术干货和N多个源码
分享,还可以第一时间获悉最新技术动态
、内测资格
、BAT大佬的经验
、精品视频教程
、副业赚钱
经验,微信搜索readdot
关注!