HarmonyOS - 自定義組件之計時器
??想了解更多關(guān)于開源的內(nèi)容,請訪問:??
前言
前段時間項目中遇到了計時器的功能,項目中的計時器其實只是顯示功能,數(shù)據(jù)全是由設(shè)備上報的。完成項目后,自己做了一個小的計時器組件,在這個過程中也發(fā)現(xiàn)了一些問題。
效果展示
組件直接傳入以秒為單位的數(shù)據(jù),最終顯示如下效果:
實現(xiàn)原理
1、用setTimeout模擬setInterval的行為
正常情況下,說到計時器首先想到的是使用setInterval,對比setTimeout要去重復(fù)調(diào)用,setInterval很方便就能實現(xiàn),如下代碼:
getTime(time) {
this.countNum = time;
setTimeout(() => {
this.getTime(time --)
}, 1000)
},
setInterval(() => {
this.countNum --
}, 1000)
但為什么要使用setTimeout呢,查下兩個定時器的原理,會發(fā)現(xiàn),創(chuàng)建一個時間間隔為100ms的定時器,setInterval每隔100ms往隊列中添加一個事件;100ms后,添加T1定時器代碼至隊列中,主線程中還有任務(wù)在執(zhí)行,所以等待,some event執(zhí)行結(jié)束后執(zhí)行T1定時器代碼;又過了100ms,T2定時器被添加到隊列中,主線程還在執(zhí)行T1代碼,所以等待;又過了100ms,理論上又要往隊列里推一個定時器代碼,但由于此時T2還在隊列中,所以T3不會被添加,結(jié)果就是此時被跳過;這里我們可以看到,T1定時器執(zhí)行結(jié)束后馬上執(zhí)行了T2代碼,所以并沒有達(dá)到定時器的效果。
綜上所述,setInterval有兩個缺點:
- 使用setInterval時,某些間隔會被跳過。
- 可能多個定時器會連續(xù)執(zhí)行。
所以,我們要使用setTimeout模擬setInterval,來規(guī)避掉上面的缺點。
2、用Date.now()獲取當(dāng)前時間,規(guī)避瀏覽器退出再進(jìn)來造成的計時誤差
當(dāng)我們在使用計時工具的時候,因為一些原因,將瀏覽器退到后臺,再次進(jìn)來的時候,發(fā)現(xiàn)計時器時間不對,感覺剛才退出去的這段時間,計時器是停止?fàn)顟B(tài)。針對這個問題,查詢會發(fā)現(xiàn),出于節(jié)能的考慮, 部分瀏覽器在進(jìn)入后臺時(或者失去焦點時), 會將 setTimeout 等定時任務(wù)暫停,待用戶回到瀏覽器時, 才會重新激活定時任務(wù)。
說是暫停,實踐操作會發(fā)現(xiàn)其實應(yīng)該說是延遲, 1s 的任務(wù)延遲到 2s, 或者更久,總之,計時器計算掉的時間,比實際過去的時間少。解決這個問題,我們可以使用Date.now()記錄時間,如下,計算兩次計時事件的時間差,來計算每次計時的step。
getTime(time) {
this.countNum = time;
setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 頁面退到后臺后計時有偏差,對比時間差,得到計時step
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},
3、及時清理定時器
這是容易忽略的一點,我們的組件唯一的一個prop屬性就是time,實際的業(yè)務(wù)場景中,可能會有一些操作改變倒計時的時長,所以我們的組件需要監(jiān)聽time值的改變,來做一些初始化操作,這時候你會發(fā)現(xiàn),當(dāng)做了2次初始化操作后,我們的代碼中會同時存在了2個計時器,時間過了1秒后2個計時器同時觸發(fā),對time值做了2次“- 1”操作,所以,我們在初始化時要清除之前的定時器。
getTime(time) {
this.timer && clearTimeout(this.timer) //清除定時器
this.countNum = time;
this.timer = setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 頁面退到后臺后計時有偏差,對比時間差,得到計時step
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},
實現(xiàn)過程
countDown組件hml部分:
<div class="count-down">
<image class="count" src="./count.png"></image>
<div class="countNumCon">
<text class="countNum">
{{countNum}}
</text>
</div>
</div>
countDown組件js部分:
export default {
props: [
'time'
],
data: {
timer: null, //定時器
curTime: 0, //記錄上次操作時間
countNum: '',
},
onInit() {
this.countDown()
this.$watch('time', 'countDown');
},
//將傳入的時間(s)轉(zhuǎn)換成天/小時/分鐘/秒
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 }
},
//處理時間格式
dealTime(time){
return `00${time || ''}`.slice(-2);
},
countDown() {
this.curTime = Date.now()
this.getTime(this.time)
},
//計算時間
getTime(time) {
this.timer && clearTimeout(this.timer)
if (time < 0) {
return
}
const { dd, hh, mm, ss } = this.durationFormatter(time)
this.countNum = `${dd || 0}天 ${this.dealTime(hh)}:${this.dealTime(mm)}:${this.dealTime(ss)}`;
this.timer = setTimeout(() => {
const nowDate = Date.now()
const diff = Math.floor((nowDate - this.curTime) / 1000)
const step = diff > 1 ? diff : 1 // 頁面退到后臺的時候不會計時,對比時間差,大于1s的重置倒計時
this.curTime = nowDate
this.getTime(time - step)
}, 1000)
},
};
countDown組件css部分:
.count-down {
display: flex;
margin-top: 200px;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 50%;
}
.countImg {
width: 200px;
height: 100px;
}
.numContainer {
background-color: black;
height: 57px;
width: 140px;
left: 8px;
top: -80px;
justify-content: center;
border-radius: 7px;
}
.count {
color: white;
font-size: 22px;
font-weight: 500;
}
父組件hml部分:
<element name="countDown" src="../countDown/countDown.hml">
</element>
<div class="container">
<countDown time="{{ leftTime }}">
</countDown>
</div>
父組件js部分:
export default {
data: {
leftTime: 100,
},
};
總結(jié)
這個計時器組件就是對日常工作的發(fā)散,作為一個鴻蒙小白,順便做一個小小的練習(xí)。