天天看點

【uniapp實戰筆記】聊天APP的開發踩坑記錄

最近工作重心轉移到了 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…