Walk you step by step to build a Vue countdown component

1. The first requirement for joining the company is to complete an activity project with a front-end boss.

Since we are developing together, of course we will not miss the opportunity to read the code of the boss.
Because I need to use the countdown function in my page, I found that the boss has already written a ready-made countdown component, so I used it directly.
It feels great to just pass a parameter and realize the function. After the project was completed, I worshiped the code of the countdown component of the boss. I really learned a lot. Listed below:
Why should the timer use setTimeout instead of setInterval
? Why not directly set the remaining time to -1.
How to return the required time (maybe I only need minutes and seconds, so just return minutes and seconds, or maybe I want them all).
Not sure whether the interface returns the remaining time or the deadline, how to accommodate both situations at the same time.
Not sure whether the time returned by the interface is in seconds or milliseconds.
Okay, you may not understand these questions, but it doesn’t matter. After reading the explanation below, I believe you will suddenly understand.

2. Start manual operation

1. First create a vue component

export default {
data: () => ({

}),
props: {

},
};

2. Implement the basic countdown component
Next, assume that the interface obtains a remaining time.

Pass the remaining time time into this countdown component. Since time may be in seconds or milliseconds, we need to pass in an isMilliSecond when passing in time to tell the countdown component that this time is milliseconds. The unit is still seconds. This is shown in the props in the code below.

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;
}
},
};

The duration in computed is the result of converting time. Regardless of whether time is milliseconds or seconds, it is converted into seconds. I don’t know if you noticed it: +this.time. Why do we need to add a '+' sign in front. This is worth learning, because the string of numbers returned by the interface is sometimes in the form of a string, sometimes in the form of a number (you cannot trust the back-end classmates too much, and you must take precautions yourself). So by adding a '+' sign in front of it, everything is converted into a number. The current duration is the converted time!
After we get the duration, we can start the countdown.

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);
},
}
};

A countDown method is created here, which means to start the countdown. The countdown method starts to be executed after entering the page.
The countDown method calls the getTime method, and getTime needs to pass in the duration parameter, which is the remaining time we get.

Now let’s implement this method.

{ {day}} days left { {hours}}:{ {mins}}:{ {seconds}}

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);
}
}
};

As you can see, the purpose of getTime is to obtain days, hours, mins, and seconds, then display them on HTML, and refresh the values ​​of days, hours, mins, and seconds in real time through the timer. Thus realizing the countdown. Very simple, is there any?
durationFormatter is a method that converts duration into days, hours, minutes, and seconds. It is very simple. You can take a look at its specific implementation.
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 }; }, well, the problem begins! !














3. Why use setTimeout to simulate the behavior of setInterval?
Wouldn't it be more convenient to use setInerval here?
setTimeout(function(){··· }, n); // Execute function after n milliseconds
setInterval(function(){··· }, n); // Execute function every n milliseconds
to see what setInterval has shortcoming:

Again, the time interval specified by the timer indicates when the timer code is added to the message queue, not when the code is executed. Therefore, the actual time when the code is executed is not guaranteed, and depends on when it is fetched and executed by the event loop of the main thread.
setInterval(function, N)
//That is: push the function event to the message queue every N seconds.
picture
As can be seen in the above figure, setInterval adds an event to the queue every 100ms; after 100ms, add the T1 timer code to the queue, and the main There are still tasks executing in the thread, so we wait. After some event is executed, the T1 timer code is executed; after another 100ms, the T2 timer is added to the queue, and the main thread is still executing the T1 code, so we wait; after another 100ms, the T2 timer is added to the queue. 100ms, theoretically another timer code needs to be pushed into the queue, but since T2 is still in the queue at this time, T3 will not be added, and the result is that it is skipped at this time; here we can see that the T1 timer executes The T2 code was executed immediately after the end, so the timer effect was not achieved.

To sum up, setInterval has two disadvantages:
when using setInterval, some intervals will be skipped;
multiple timers may be executed continuously;
it can be understood this way: the tasks generated by each setTimeout will be directly pushed to the task queue; And setInterval must make a judgment every time before pushing a task to the task queue (to see if the last task is still in the queue).
Therefore, we generally use setTimeout to simulate setInterval to avoid the above shortcomings.

4. Why is clearTimeout(this.timer) required?
Second question: Why is there the sentence this.timer && clearTimeout(this.timer);?
Assume a scenario:
As shown in the figure, there are two buttons in the parent component of the countdown. Clicking activity one will pass in the remaining time of activity one, and clicking activity two will pass in the time of activity two.
Insert image description here
If the countdown component is counting down activity one at this time, and then clicks activity two, a new time will be input immediately, and the time needs to be re-timed. Of course, the timing will not be re-timed here, because the mounted component will only be executed once. In other words, this.countDown(); will only be executed once, which means this.getTime(this.duration); will only be executed once, so duration is still the time of activity one. What should I do? The watch comes in handy.

Let's monitor the duration. If we find that the duration changes, it means that the new time has been passed into the component. At this time, we need to call this.countDown() again.
code show as below:

{ {day}} days left { {hours}}:{ {mins}}:{ {seconds}}

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);
}
}
};

Okay, but it doesn’t explain the question raised above: Why is there this.timer && clearTimeout(this.timer);?
In this way, assuming that the page now displays the time of activity one, at this time, when setTimeout is executed, the callback function in setTimeout will be placed in the task queue after one second. Note that it is after one second! At this time, however, at the beginning of this second, we clicked the activity two button. At this time, the time of activity two will be passed into the countdown component, and then countDown() will be triggered, which also calls this.getTime(this.duration );, and then execute setTimeout, the callback function will be placed in the task queue after one second.
At this time, there will be two setTimeout callback functions in the task queue. After one second has passed and the two callback functions are executed one after another, we will see that the time on the page suddenly decreases by 2. In fact, the operation of decrementing by 1 is performed twice very quickly.

This is why the sentence this.timer && clearTimeout(this.timer); is added. Just clear the previous setTimeout.

5. Use diffTime.
When you think this is a perfect component, you want to use this component on the project. Assuming that you actually use it and it is online, you will find a big problem: when the page is opened At that time, the countdown started, and the time was 12:25:25, with 1 day left, and then someone sent you a WeChat message. You immediately switched to WeChat, replied to the message, and then switched back to the browser, only to find that the countdown time still had 1 day 12 left: 25:25. You panic: there is a bug in the code you wrote!

How is this going?
For the sake of energy saving, some browsers will pause scheduled tasks such as setTimeout when entering the background (or losing focus) and wait until the user returns to the browser before reactivating the scheduled tasks. It is said to be paused, but in fact it should be said to be delayed
. , 1s tasks are delayed to 2s, 2s tasks are delayed to 5s, the actual situation varies between browsers.
So that’s it, it seems like it can’t be as simple as just decreasing it by 1 every time (after all, setTimeout cools down after you switch the browser to the background, wait a few seconds, switch it back, and then execute setTimeout, it just decreases one second).

So we need to rewrite the getTime method.

{ {day}} days left { {hours}}:{ {mins}}:{ {seconds}}

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);
}
}
};

As you can see, we added new code in three locations.
First, add the curTime variable to data, and then assign Date.now() to curTime when executing countDown, which is the current moment, which is the moment displayed on the page.

Then look at the third modified code. You can see that -1 was changed to -diffTime.
now is the moment when the setTimeout callback function is executed.
Therefore, diffTime represents the time period between the execution time of the current setTimeout callback function and the last change of the remaining time on the page. In fact, it is the time period between the execution time of the current setTimeout callback function and the execution time of the previous setTimeout callback function.

Maybe you still don't understand diffTime. For example:
you open this countdown page and execute countDown, which means you have to execute the getTime method. That is, the following code will be executed immediately.
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
After executing these codes, the remaining time will appear on the page.

And this.curTime = Date.now(); records the time point at this moment.
Then execute the callback function in setTimeout one second later:
const now = Date.now(); Record the time point at which the current setTimeout callback function is executed.
const diffTime = Math.floor((now - this.curTime) / 1000); Records the period of time remaining from the current execution time of the setTimeout callback function to the start of rendering on the page. In fact, the diffTime at this time is =1.
Then this.curTime = now; changes the value of curTime to the current time point when the setTimeout callback function is executed.
this.getTime(duration - diffTime); is actually this.getTime(duration - 1);

Then execute getTime again, the following code will be executed again, and the new remaining time will be rendered.
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
Then the setTmieout callback function will be executed one second later, here Before one second is over, we switch the browser to the background, and setTimeout cools down at this time. Wait 5 seconds before switching back. So the callback function of setTmieout can be executed.

At this time, const now = Date.now(); records the time point at which the current setTimeout callback function is executed.
CurTime is the execution time of the previous setTimeout callback function.
So const diffTime = Math.floor((now - this.curTime) / 1000); In fact, the value of diffTime is 5 seconds.
Therefore this.getTime(duration - diffTime); is actually this.getTime(duration - 5);
This perfectly solves the problem of the remaining time remaining unchanged because the browser switches to the background.

6. Add new function: expiration time can be passed in.
Previously, only the remaining time could be passed in, but now it is hoped that the expiration time can also be passed in.

Just change the duration.

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; } }, determine whether the length of the incoming end is greater than 13 Determine whether it is seconds or milliseconds. easy!










7. Add new features: You can choose what to display, such as only seconds or hours.
Just change the html:

It's very clever. You only need to use the slot to pass the countdown component, that is, the value of the child component to the parent component.

See how the parent component uses this component.

{ {timeObj.d}} days { {timeObj.hh}} hours { {timeObj.mm}} minutes { {timeObj.ss}} seconds
Look, so clever and simple.

I found that the writing method 00${hours}.slice(-2) is also worth learning. In the past, when the minutes were obtained, one had to manually judge whether the minutes obtained were two digits or one digit. If it was one digit, one had to manually add 0 in front. Like the following code:

var StartMinute = startDate.getMinutes().toString().length >= 2 ? startDate.getMinutes() : '0' + startDate.getHours();
and 00${hours}.slice(-2) does not need to be judged, First add 0, then cut off two digits from the back to the front.
At this point, a perfect countdown component is completed.

3. Learning summary

1. Understand the shortcomings of setInterval and use setTimeout instead of setInterval.
2. I learned the "+" operation, no matter what, convert the long string of numbers obtained from the interface into numbers to ensure safety.
3. Use clearTimeout to clear the previous timer to prevent any impact.
4. Learn to use v-slot to pass values ​​from child to parent.
5. Learn a countdown component to facilitate CV operations in the future. Paste the complete code of the component:

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; // The page will not count down when it returns to the background. Compare the time difference and reset the countdown if it is greater than 1s this.curTime = now ; this.getTime(time - step); }, 1000); } } };









If you have learned anything, please add it! !

Everyone is welcome to leave a message for discussion, and I wish you smooth work and a happy life!

おすすめ

転載: blog.csdn.net/longxiaobao123/article/details/132968274