最近工作重心轉移到了 uniapp,有一說一,這個架構跨端确實優秀,一套代碼能一次編譯到多端使用。
但随之而來的是層出不窮的相容性問題,同樣地在面臨APP底層的改動也顯得力不從心。同時,uniapp 的性能問題也是一直被人所诟病的,在這方面上,一還是要提高自己本身的編碼能力,二還是得依靠 dcloud 團隊持續優化架構。
本期是給大家分享使用 uniapp 開發一個聊天APP的踩坑問題。
一、輸入框吞字,光标閃動問題
uniapp 中使用輸入框,無論是 input 元件還是 textarea 元件,都存在一個問題。
如果元件綁定 v-model 的話,會有兩個BUG:
①輸入時,在蘋果手機或者使用部分特殊有待選區域的輸入法,會存在輸入框吞字;
②從文字中間輸入時,光标會閃動到最後。
該問題的解決方案,最終隻能使用 :value 去綁定輸入框,為輸入框配置設定兩個變量,一個是真實的 value 值,一個是臨時的 tempValue 值。
臨時的 tempValue 值用于在輸入觸發的 @input 事件内實時接收,真實的 value 值隻在第一次由無值到有值的時候接收一次,後面隻需在發送時設定真實 value 值為空即可清空輸入框。
<template>
<textarea :value="value" @input="handleInput"></textarea>
<button @click="handleClickSend">發送</button>
</template>
export default {
data: {
value: '',
tempValue: ''
}
handleInput(event) {
const value = event.detail.value
if(!this.value) {
// 第一次值為空時賦給真實值
this.value = value
}
this.tempValue = value // 臨時值用于實時接收
}
asynchandleClickSend() {
if(!this.value) {
return
}
// 發送時,臨時值存儲的為目前輸入框内的值
// 随後将真實value值設定為空實作清空輸入框
// 模拟發送請求
const res = await this.$axios.post({
url: xxxx,
text: this.tempValue
})
this.value = '' // 清空輸入框
}
}
二、元件key值問題
部落客封裝了一個消息氣泡渲染元件,隻需要将必要的參數傳遞進去即可渲染各種消息,但是在開發過程中,發現了氣泡消息抛出來長按事件所帶的參數錯亂的問題,後經研究為元件加上了 key 值才解決。
<template>
<viewclass="chat-warp"v-for="item of msgList":key="item.id">
<!-- 注意,需要給元件也給key值 -->
<chat-item:item="item":key="item.id"></chat-item>
</view></template>
可能是 vue 中對元件的更新機制不同,是以 v-for 循環中的元件也需要給 key 值。
三、消息定位問題
項目中的聊天頁,使用的是 scroll-view 配合封裝好了的富文本元件來渲染各種消息,由于下拉加載更多消息時,總會有螢幕閃動的現象出現,是以最後是兩層 scroll-view 來配合使用,一層是真消息,一層是假消息。
加載更多時,假消息顯示、真消息隐藏,等到消息完全渲染定位完畢後再隐藏假消息、顯示真消息。
(1)滾動不到最底部的問題
在開發過程中,常常會遇到發送消息或者進入聊天頁時,滾不到最底部的情況。
針對這個問題,一開始是不斷地瞄點滾動到底部,一進入頁面就會觸發七八次 goBottom 函數。
後面經過優化,整理了思路,調整為“判斷消息是否完全渲染完畢後,再執行goBottom 函數”。
而判斷消息是否完全渲染完畢,則需要用到 uni.createSelectorQuery() 這個 API。
// 在onReady鈎子中調用goBottom
onReady() {
this.$nextTick(() => {
this.goBottom()
})
}
// 滾動到底部函數
goBottom() {
this.scrollView = '' // scrollview瞄點置空
this.$nextTick(async () => {
const res = await this.checkMsgIsRender('btm') // 檢測最底部的消息是否完全渲染完畢
if(res) {
this.scrollView = 'bottom' // 瞄點至底部
} else {
this.goBottom()
}
})
}
// 檢測消息是否渲染完畢
checkMsgIsRender(position) {
let msgID = ''
if(position === 'btm') {
// 底部
// 找到最底部的消息ID
msgID = xxxx
} else {
// 頂部
// 找到最頂部的消息ID
msgID = yyyy
}
// 傳回一個Promise
return new Promise((resolve) => {
const query = uni.createSelectorQuery().in(this)
query.select('#id').boundingClientRect(data => {
// 存在data,且存在寬和高,視為渲染完畢
if(data && data.width && data.height) {
resolve(true)
} else {
resolve(false)
}
}).exec();
})
}
(2)下拉加載更多消息瞄點定位不準的問題
同樣地,“下拉加載更多消息”也是存在瞄點不準确的問題,而一開始的解決方案,是簡單粗暴地延時兩秒鐘,這直接導緻了使用者出現等待時間過長的問題,而且還不一定能準确定位到。
參照滾動到底部的做法,我們舉一反三,通過判斷消息是否完全渲染完畢來決定瞄點的時機,以確定瞄點定位準确。
// 加載更多
async loadMore() {
await this.getMoreMsg() // 向伺服器擷取更多消息或者展示本地消息,該函數不展開
let location = async () => {
const res = await this.checkMsgIsRender('top') // 檢測最頂部的消息是否完全渲染完畢
if(res) {
// 這裡還需要找到需要瞄點過去的消息ID,zzzz
this.scrollView = zzzz // 瞄點到消息zzzz
} else {
location ()
}
}
this.scrollView = '' // scrollview瞄點置空
this.$nextTick(() => {
location ()
})
}
總結
雖然網上對 uniapp 一直存有質疑的聲音,但是講句道理,能夠以一套代碼運作到多端的技術還是挺厲害的,盡管目前來說這項技術還不是很成熟(性能問題、相容問題)。
uniapp 能帶領我們的項目走向多遠,還是可以期待的,當然不能光指望 dcloud 團隊,個人代碼的寫法也很關鍵,一起繼續努力,Keep learning…