Эта статья была впервые опубликована на:GitHub.com/big-oh-front E…Добро пожаловать, чтобы обратить внимание и перепечатать.
Введение
Первым требованием для вступления является завершение проекта активности с фронтенд-боссом.
Поскольку они разрабатывают вместе, то, конечно же, не упустят возможности прочитать код большого парня.
Поскольку мне нужно использовать функцию обратного отсчета на моей странице, я обнаружил, что большой парень уже написал готовый компонент обратного отсчета, поэтому я использовал его напрямую.
Приятно передать параметр и реализовать функцию. После того, как проект был завершен, я преклонялся перед кодом компонента обратного отсчета большого парня. Действительно позвольте мне узнать много. Перечислено ниже:
- Почему таймеры используют setTimeout вместо setInterval
- Почему бы не напрямую превратить оставшееся время -1.
- Как вернуть требуемое время (может мне нужны только минуты и секунды, потом только минуты и секунды, а может я хочу их все).
- Не уверен, возвращает ли интерфейс оставшееся время или крайний срок, как учесть оба случая.
- Не уверен, что время, возвращаемое интерфейсом, в секундах или миллисекундах.
Что ж, вы можете не понимать эти вопросы, но это не имеет значения, после прочтения объяснения ниже, я думаю, вы вдруг поймете.
2. Начните мануальные упражнения
1. Сначала создайте компонент vue
<template>
<div class="_base-count-down">
</div>
</template>
<script>
export default {
data: () => ({
}),
props: {
},
};
</script>
<style lang='scss' scoped>
</style>
2. Основные компоненты обратного отсчета
Далее предположим, что интерфейс получает оставшееся время.
оставшееся время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
Продолжительность in является результатом преобразования времени независимо от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, что означает запуск обратного отсчета, а метод обратного отсчета выполняется при входе на страницу.
countDown
Метод вызывает метод getTime, и getTime должен передать параметр продолжительности, который представляет собой оставшееся время, которое мы получаем.
Теперь реализуем этот метод.
<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>
можно увидеть,getTimeЦель состоит в том, чтобы получить дни, часы, минуты, секунды, а затем отобразить их в html и в режиме реального времени обновить это значение в днях, часах, минутах, секундах с помощью нескольких таймеров. Для того, чтобы добиться обратного отсчета. Очень просто, с дровами есть?
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.?
Не удобнее ли использовать setInerval здесь?
setTimeout(function(){··· }, n); // n毫秒后执行function
setInterval(function(){··· }, n); // 每隔n毫秒执行一次function
Вы можете увидеть, каковы недостатки setInterval:
Опять же, интервал, указанный таймером, указывает, когда код таймера добавляется в очередь сообщений, а не когда код выполняется. Таким образом, реальное время выполнения кода не гарантируется, оно зависит от того, когда он будет выбран циклом событий основного потока и выполнен.
setInterval(function, N)
//即:每隔N秒把function事件推到消息队列中
Как видно из рисунка выше, setInterval добавляет событие в очередь каждые 100 мс, через 100 мс в очередь добавляется код таймера T1, а в основном потоке все еще выполняются задачи, так что подождите и выполните таймер T1. код после завершения выполнения какого-то события; еще через 100 мс в очередь добавляется таймер T2, а основной поток все еще выполняет код T1, так что подождите; еще через 100 мс, по идее, в очередь будет помещен еще один код таймера , а потому что Т2 в это время еще есть.В очередь Т3 добавляться не будет, а результат в это время пропускается, здесь мы видим, что код Т2 выполняется сразу после исполнения таймера Т1, поэтому эффект таймера не достигается.
Подводя итог, setInterval имеет два недостатка:
- При использовании setInterval некоторые интервалы пропускаются;
- Возможно, что несколько таймеров будут работать непрерывно;
Это можно понять так:Каждая задача, сгенерированная setTimeout, будет помещена непосредственно в очередь задач, а setInterval вынесет решение перед тем, как каждая задача будет помещена в очередь задач (проверить, находится ли последняя задача в очереди)..
Поэтому мы обычно используем setTimeout для имитации setInterval, чтобы избежать вышеуказанных недостатков.
4. Почему clearTimeout(this.timer)
Второй вопрос: почемуthis.timer && clearTimeout(this.timer);
Это предложение?
Предположим сценарий:
Как показано на рисунке, в родительском компоненте обратного отсчета есть две кнопки.Нажатие на действие 1 перейдет в оставшееся время действия 1, а нажатие на действие 2 пройдет на время действия 2.
Если компонент обратного отсчета выполняет обратный отсчет действия 1 в это время, а затем щелкает действие 2, новое время будет передано немедленно, и в это время его необходимо повторно засечь. Разумеется, здесь нет ретайминга, потому что монтирование компонента будет выполнено только один раз. то естьthis.countDown();
выполняется только один раз, т.this.getTime(this.duration);
Это будет выполнено только один раз, поэтому продолжительность по-прежнему равна времени действия 1. Что мне делать? часы пригодятся.
Давайте проследим за длительностью, если обнаружено изменение длительности, это означает, что новое время передано в компонент, в это время следует снова вызвать this.countDown().
код показывает, как показано ниже:
<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);
Это предложение?
Таким образом, предполагая, что текущая страница отображает время активности, в это время выполните setTimeout, всекундой позжеФункция обратного вызова в setTimeout будет помещена в очередь задач,Обратите внимание, что через секунду! Однако в это время, в начале этой секунды, мы нажали кнопку Activity 2, время Activity 2 в это время будет передано в компонент обратного отсчета, а затем сработаетcountDown()
, который также вызываетthis.getTime(this.duration);
, а затем выполните setTimeout, а также поместите функцию обратного вызова в очередь задач на одну секунду позже.
В это время в очереди задач будет две функции обратного вызова setTimeout. Ожидая, пока пройдет одна секунда, две функции обратного вызова выполняются одна за другой, и мы увидим, что время на странице внезапно сокращается на 2. На самом деле операция вычитания 1 выполняется дважды очень быстро.
Вот почему добавлениеthis.timer && clearTimeout(this.timer);
Причина этой фразы. Является ли очистить последний setTimeout.
5. Использование diffTime
Когда вы думаете, что это идеальный компонент, вы хотите использовать этот компонент в проекте, предполагая, что вы его используете, и он находится в сети, и вы обнаруживаете, что есть большая проблема: когда страница открывается, начинается обратный отсчет. , время还剩1天12:25:25
А тут кто-то прислал вам микрописьмо, вы сразу переключаетесь на микрописьмо, после вырезания ответного сообщения возвращаетесь в браузер, но все же нашли время обратного отсчета还剩1天12:25:25
. Вы паникуете:В вашем коде ошибка!
Как это происходит?
Ради экономии энергии некоторые браузеры уходят в фоновый режим (или теряют фокус),Приостановит выполнение запланированных задач, таких как setTimeout Запланированная задача не будет повторно активирована, пока пользователь не вернется в браузер.
Говорят, что это приостановлено, на самом деле это должно быть задержкой.Задача 1 с откладывается до 2 с, а задержка 2 с - до 5 с. Фактическая ситуация варьируется от браузера к браузеру.
Оказывается, не может быть так просто каждый раз вычитать 1 (ведь после того, как вы переключите браузер в фоновый режим, setTimeout остынет, подождите несколько секунд, чтобы переключиться обратно, а затем выполните setTimeout, что составляет всего одну секунду).
Поэтому нам нужно переписать метод getTime.
<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, а затем отдайте ее при выполнении countDown.curTime
назначатьDate.now()
, то есть текущий момент, то есть момент, отображаемый на странице.
Затем посмотрите на модифицированный третий код. Можно видеть, что-1
изменился на-diffTime
.
сейчас момент, когда функция обратного вызова setTimeout выполняется.
Таким образом, diffTime означаетТекущее время, когда функция обратного вызова этого setTimeout выполняется, является периодом времени последнего изменения от оставшегося времени на странице.. На самом деле, этоТекущее время выполнения функции обратного вызова setTimeout — это период времени, когда выполняется функция обратного вызова предыдущего setTimeout.
Возможно, вы все еще не совсем понимаете diffTime. Например:
Вы открываете страницу обратного отсчета и выполняете countDown, что означает, что вам нужно выполнить метод getTime. То есть следующий код будет выполнен сразу.
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
Оставшееся время отображается на странице после выполнения кода.
а такжеthis.curTime = Date.now();
Только что заснял момент.
Затем выполните функцию обратного вызова в setTimeout через одну секунду:
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);
А затем выполнить getTime, он повторно выполнит приведенный ниже код, происходит новый рендеринг оставшегося времени.
this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
Затем, через одну секунду, необходимо выполнить функцию обратного вызова setTmieout. До истечения этой секунды мы переключаем браузер в фоновый режим, и setTimeout остывает. Подождите 5 секунд перед переключением обратно. Таким образом, функция обратного вызова setTmieout может быть выполнена.
В настоящее времяconst now = Date.now();
Запишите текущий момент времени, когда функция обратного вызова этого setTimeout выполняется.
А curTime — это время, когда была выполнена последняя функция обратного вызова 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;
}
},
Определите, больше ли длина входящего конца 13, чтобы определить, является ли это секундами или миллисекундами. легкий!
7. Добавьте новые функции: вы можете выбрать, что отображать, например, только секунды или только часы.
Просто нужно изменить html:
<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) Этот способ написания тоже стоит изучить. В прошлом при получении минут необходимо было вручную определить, была ли полученная минута двузначной или однозначной. Как код ниже:
var StartMinute = startDate.getMinutes().toString().length >= 2 ? startDate.getMinutes() : '0' + startDate.getHours();
а также00${hours}
.slice(-2) не нужно судить, сначала добавьте 0, а затем перехватите две цифры сзади.
здесь.
Идеальный компонент обратного отсчета завершен.
3. Резюме обучения
- Поймите недостатки 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>
Что вы узнали, добро пожаловать, чтобы добавить! !
Добро пожаловать всем, чтобы оставить сообщение и обсудить, я желаю вам гладкой работы и счастливой жизни!
Я передний конец bigo, увидимся в следующий раз.