天天看點

微信小程式開發中的二三事之網易雲信IMSDK DEMO

本文由作者鄒永勝授權網易雲社群釋出。

簡介

為了更好的展示我們即時通訊SDK強悍的能力,網易雲信IM SDK微信小程式DEMO的開發就提上了日程。用産品的話說就是:

  • 雲信 IM 小程式 SDK 的能力示範
  • 提供開發者小程式開發參考

換句話說就是在微信裡面通過我們雲信的IM SDK再實作一個mini版微信。整個小程式主要功能點總的來說是:

  • 登入注冊(為了實作不同端同一賬号體系,是以沒有采用微信授權登入)
  • 最近會話展示
  • 通訊錄
  • 單聊對話
  • 使用者名片

廢話不多說直接上圖:

一期已經上線,不足的地方,懇請斧正

本文從基礎開始介紹在開發雲信DEMO的過程中的一些難點、整體的結構設計、思考的一些解決方案以及踩過的一些坑,希望對大家有些幫助當然希望更多人接入網易雲信SDK。

基礎

小程式開發基本零門檻,難度基本與模闆語言相當,如果你有使用MVVM架構開發前端的經驗,基本花個半小時過一遍微信小程式官方文檔,即可入門,具體開發細節可以邊做邊查本人就是這樣的。。。。

首先需要明白小程式的運作環境,它運作在微信的上下文中,處于微信這個沙盒中,沒有window對象,不能通路基于browser context下的DOM。在ios裝置上是運作在JSCore(蘋果開發),在android裝置上則是在X5(騰訊基于webkit開發),在開發工具中運作在nwjs(同類型還有electron)

一個标準的小程式是由一個應用執行個體以及多個頁面執行個體構成。仔細想來微信小程式不就是由多個互相關聯的頁面組成的嘛,在每個頁面中,需要考慮與外部以及與其他頁面進行互動。本着“3W+1H”原則,是以也就可以提煉出在開發整個IM DEMO過程中需要關注的點:

  • 如何定義頁面、修改樣式
  • 頁面怎麼進行螢幕适配
  • 多頁面間怎麼進行通信
  • 每個頁面的生命周期過程
  • 如何定義元件、元件間如何通信
  • 局部與全局狀态的通信
  • 互動事件的處理
  • 官方提供的一些元件以及能力

0x01 靜态頁面

在微信官網下載下傳開發工具,然後建立一個小程式工程,會發現項目根目錄下會有一個 app.json和project.config.json以及pages/logs 目錄下的 logs.json,這裡來闡述下它們的差別:

  • app.json 是對目前小程式的全局配置,包括了小程式的所有頁面路徑、界面表現、網絡逾時時間、底部 tab 等,具體每一項代表什麼可以檢視
  • project.config.json是針對小程式開發工具的一個配置檔案,記載了你針對開發工具配置進行的一些修改,例如界面顔色、編譯配置等,詳細配置可檢視這裡
  • page.json頁面級的配置檔案,可以單獨定義該頁面的一些屬性,例如頂部顔色、是否可下拉、使用元件定義等,詳細配置可檢視這裡

闡述完各種配置檔案之後,可以開始進行頁面的程式設計。傳統的網頁程式設計采用的三劍客 HTML+CSS+JS來實作,在微信小程式中同樣有三劍客 WXML+WXSS+JS。

  • WXML與HTML十分類似,可以說就是帶有模闆文法,經過微信封裝的自定義标簽的集合。
    • 操碎了心的微信給我們封裝了很多元件,例如view、button、text、map、video、audio等等,全部通過自定義标簽的方式實作,部分元件渲染提升為原生元件,提高了整體效率(也帶來了不少麻煩)。
    • 當然整個頁面上還支援個人十分喜愛的一種模闆文法-Mustache文法,與Vue類似,你可以在表達式中通路在data中已經定義好的資料,一旦資料發生變化,綁定的頁面會自動重新整理,實作渲染與邏輯分離。當然還需要條件、循環等控制能力,這些在整個模闆中都有,更為詳細的可以檢視文檔
  • WXSS說白了就是弱化版CSS,并在此基礎上增加了尺寸機關rpx,以此為基礎實作螢幕的适配(具體原理與rem方案适配螢幕類似);可以在頁面wxss定義頁面級樣式,在app.wxss定義全局樣式;僅僅支援部分css選擇器(

    要特别注意

    )
  • JS和我們寫網頁的有些差別,所有的方法、屬性均以Page執行個體中的對象屬性的形式存在,我們可以在此聲明微信提供的頁面生命周期鈎子、自定義方法以及頁面資料。需要注意的是js中沒有與DOM和BOM相關對象以及屬性,也就是常見的window、document等是沒有的。後面會闡述如果你想擷取dom結構以及樣式時的解決方案。

0x02頁面間通信

整個小程式是由多個頁面組成,有時候會遇到需要跨頁面進行通信的場景,例如聊天跳轉到聊天界面、删除、拉黑好友後通知外部進行好友清單的重新整理等等。思考後有如下幾種方式可供參考:

  • 頁面跳轉情況下可以通過querystring進行資料的傳遞。缺點是資料量受到querystring大小的限制而且僅僅局限于頁面間跳轉。
  • 全局狀态下存儲,每次變化後修改全局資料(localStorage或globalData),然後頁面每次onShow時檢查此全局資料,并做出相應的反應。缺點是顯而易見的,業務複雜時,備援代碼十分多,且需要觸發onShow方法,存在一定的局限性。

要想滿足耦合性小、不局限于頁面跳轉通信、通信資料量不受限制這些需求,很明顯

釋出/訂閱模式(觀察者模式)

符合我們的要求。既能做到時間上解耦又能做到對象間解耦。iOS端的Notification Center以及android端的EventBus都是通過這一設計模式來處理跨頁面間通信的的需求的。然後微信小程式内部并沒有內建這一事件通知機制,是以需要手動去實作一個并将其與微信小程式的頁面生命周期結合起來。

觀察者模式是由排程中心、釋出者、訂閱者組成。訂閱者會先在排程中心訂閱某一特定事件并注冊對應的回調函數,當釋出者釋出了該事件後,訂閱中心就會取出訂閱了該事件的所有訂閱者注冊的回調函數進行執行。

觀察者模式不難實作,重點是如何在微信小程式中搭配其特有的生命周期來使用。本項目在使用者登入以及注冊成功時會初始化消息訂閱中心,并全局(globalData)儲存,使得訂閱器一直駐存在記憶體中,調用時直接從globalData調用即可。當然這其中還存在一些小問題,在頁面進行切換時需要注意訂閱者、釋出者之間的時序,比如訂閱早于釋出或者釋出之後還未訂閱的情況。後期會詳細介紹該種模式的實作過程敬請期待。

0x03互動事件

傳統的DOM事件傳遞類型有冒泡型與捕獲型,微信小程式中自然也有。通常會使用bind、catch(冒泡)和capture-bind、capture-catch(捕獲)字首來裝飾具體的互動事件,兩者的差別如下:

  • bind綁定的事件

    不會

    阻止冒泡事件冒泡
  • catch綁定的事件

而小程式支援的事件類型與傳統的H5的差不多,新增了長按事件以及css動畫相關觸發(類似于Vue的js動畫鈎子)事件,具體為觸摸事件touchstart、touchmove、touchcancel、touchend、tap;長按事件longpress、longtap;動畫相關事件transitionend、animationstart、animationiteration、animationend;3Dtouch事件touchforcechange

整個事件命名還是較為清晰,基本做到了見名知意,詳細可以檢視文檔

頁面跳轉時觸發的鈎子以及Page執行個體的生命周期,請自行檢視官網,這裡不再贅述,這部分内容同樣重要。

說完了事件,肯定要說事件傳參了,方法主要有兩種:

  • 綁定到标簽上,然後在event對象中擷取(具體是target或currentTarget則視情況而定)
  • 使用頁面狀态資料或者全局資料

0x04 自定義元件

從小程式基礎庫版本 1.6.3 開始,它支援了元件化程式設計。元件類似于每一個頁面,同樣由四個檔案構成json、wxml、wss、js,隻在js中預設的一些鈎子函數變化了、json中定義變化了,wxml和wss基本類似。多說一句,元件wxss中不應使用ID選擇器、屬性選擇器和标簽名選擇器被你幹了這麼多,還剩啥。。。下面就來展開講講

對于json檔案的話需要将 

component 字段設為 true

對于wxml檔案,它的寫法與頁面模闆相同。元件模版與資料拼接後生成的節點樹,将被插入到元件的引用位置上。但是元件模闆多出一個功能就是:支援

slot

。用過vue的對它肯定十分熟悉,在制作容器元件(承載元件使用者提供的wxml結構)時用起來十分友善。同時它還支援多插槽(name區分),隻需在js檔案中聲明下即可。

對于wxss檔案,寫法與css類似,隻是有幾點差別:作用域僅僅局限于元件内;隻使用class選擇器(其他選擇器要麼不支援,要麼在特殊情況下會有問題);除了繼承樣式外,例如font、color等,全局樣式(app.wxss)對自定義元件無效。至于外部引入樣式則從 1.9.90 基礎庫才開始支援。。。

對于js檔案則與前面的頁面類似,整個js檔案基本就是一個自定義組建的構造器,調用構造器可以指定元件的屬性、資料、方法等。比較常用的有:

  • properties:Object Map
    • 外部傳入元件的屬性,用于模闆渲染,可設定三個字段, type 表示屬性類型、 value 表示屬性初始值、 observer 表示屬性值被更改時的響應函數(注意需要使用駝峰法寫法,在wxml中則使用連字元寫法)
  • data:Object
    • 元件内部資料,用于模闆渲染
  • methods:Object
    • 元件的方法,包括事件響應函數和任意的自定義方法
  • 生命周期鈎子
    • created: 元件執行個體進入頁面節點樹時執行,

      此時不能調用 setData

    • attached: 元件執行個體進入頁面節點樹時執行
    • ready: 元件布局完成後執行,此時可以使用 SelectorQuery擷取節點資訊
    • moved: 元件執行個體被移動到節點樹的另一個位置時執行
    • detached:元件執行個體在頁面節點樹被移除時執行
  • behaviors:String Array
    • 元件間代碼複用機制(類似于mixins)

元件執行個體this可以自元件方法、生命周期、屬性observer中通路。通過元件執行個體可以擷取許多有用的屬性和方法,例如is(元件檔案路徑)、triggerEvent(觸發事件,外部可監聽)、setData(設定data并渲染視圖)等

了解了元件的實作過程,接下來就是使用。用法很簡單,隻需在json檔案中聲明元件,然後在wxml中引入使用即可。

// index.json 引入元件,并定義引用名字{  "usingComponents": {    "input-modal": "/path/to/inputmodal"
  }
}// index.html 引入元件并傳入屬性以及監聽事件<input-modal title="輸入提醒"     catch:inputModalClick="tipClickHandler">
    <view>内部slot</view>
    </input-modal>// index.js 實作事件監聽函數Page({
    tipClickHandler(e) {
        console.log('自定義元件事件');
    }
})      

工程結構

整個微信小程式DEMO目錄結構如下:

|- components 自定義元件目錄
|- images 項目中使用的一些高頻次圖檔
|- pages 主功能一級頁面
    |- contact 通訊錄頁
    |- login 登入頁
    |- recentchat 最近會話頁
    |- register 注冊頁
    |- setting 設定頁
|- partials 二級頁面
    |- addfriend 添加好友頁
    |- blacklist 黑名單頁
    |- chatting 聊天頁
    |- forwardcontact 轉發消息通訊錄頁 
    |- historyfromcloud 雲端曆史記錄頁
    |- messagenotification 消息通知中心頁
    |- modify 修改個人資料頁
    |- personcard 非陌生人個人名片頁
    |- strangercard 陌生人個人名片頁
|- utils 存放一些工具類js
    |- config.js 存放項目的基本配置
    |- emojimap.js emoji文本與對應圖檔的映射關系,自定義emoji元件使用
    |- event.js 觀察者模式具體實作
    |- imageBase64.js 存儲一些小圖示bese64編碼
    |- imeventhandler.js 網易IM SDK初始化以及對應的回調函數注冊,通過消息釋出、訂閱與外部通信
    |- pinyin.js 擷取漢字的拼音
    |- util.js 一些工具方法的集合
|- vendors 引入外部的庫,主要有網易雲信 IM 的SDK以及md5加密
|- app.js 小程式根執行個體,存儲了全局中的一些資料
|- app.json 注冊頁面以及定義頁面一些基本樣式
|- app.wxss 全局樣式
|- project.config.json 設定整個小程式工程的一些屬性,包括編譯類型(截止2018年3月新增加了微信插件)、基礎庫版本等      

技術棧的一些思考

這裡探讨下目前(截止2018年3月)比較流行的三種開發微信小程式的方式:微信小程式原生、wepy、mpVue

微信小程式 wepy mpvue
開發規範 小程式開發規範 vue開發規範
狀态管理 vuex
元件化 比較原始 自定義元件規範 vue元件
多端複用 不可 可轉化為H5
建構方式 開發工具内置自動建構 架構内置 webpack
建構原理 開發工具自動建構 建構為dist後轉化為小程式支援類型然後将開發工具指向dist目錄,支援熱更新

接着分析下雲信IM DEMO的需求,首先受限于同一裝置下一個使用者的Storage的上限為10MB,是以這邊不做聊天資料的持久化,所有的聊天資料、使用者資料存儲在記憶體中,在小程式被微信關閉(駐留背景過久)或者使用者手動關閉(殺了微信程序)時所有資料會被重置;其次本期需求主要為p2p單聊,後期還會添加上群聊功能等功能,是以這邊整體代碼量需要控制,不能引入非必要架構;本期需求支援的消息類型有文本、emoji、地圖、視訊、語音、圖檔,部分元件可以借助微信提供的能力,加速渲染。。。

接下來大緻評估下實作每個頁面的技術點

一級頁面:

  • 最近會話頁
    • 滑動删除 - 自定義元件實作
    • 單條消息條目 - 全局拿到資料,然後進行清洗渲染
    • 消息通知 - 消息訂閱器
  • 通訊錄頁
    • 昵稱排序 - 漢字轉拼音
    • 新增、拉黑、删除好友 - 消息訂閱器
  • 設定頁
    • 展示個人資料 - 資料清洗
    • 修改個人資料 - 調用照相、相冊接口實作修改頭像以及其他類型資料
  • 登入注冊頁

二級頁面:

  • 聊天頁
    • 聊天界面布局
    • emoji鍵盤 - 自定義emoji元件(圖檔資源存儲在網易nos上)
    • 多種消息類型 - 支援語音、地理位置、文本、圖像、視訊、猜拳、emoji消息,本質就是實作了一個

      富文本渲染自定義元件

      ,能夠有效渲染不同的消息類型
    • 支援消息的多種手勢操作,支援消息的撤消、删除、轉發操作,單擊不同類型消息實作語音、視訊消息的播放
  • 個人資料
    • 分為兩種,一種是陌生人個人資料、一種是好友個人資料,兩種不同類型頁面展示的頁面元件是不一緻的
    • 入口分為如下幾種:單擊通訊錄條目進入好友資訊清單;單擊聊天記錄頭像進入好友清單;添加好友,結果不同則展示不同的類型使用者資料
  • 修改個人資料頁
    • 支援修改頭像、昵稱、性别、生日、手機、郵箱、簽名,盡可能做到最大的複用
  • 黑名單清單
  • 消息通知界面
    • 自定義頂部tabbar元件
  • 聊天曆史記錄界面

初步結合架構特點以及幾大開放方式特性,矛頭重點集中在如何解決應用狀态管理上面,經過評估後功能點較多,是以需要盡可能的減少引入外部架構,是以這邊在微信小程式的基礎上實作全局存儲一個消息訂閱器,然後在每個功能頁面中訂閱相應的事件,在相應的地方釋出對應的事件。這樣就解決了狀态管理這個痛點。對于其他的一些差別個人覺得沒有任何問題,對于一個有過現代前端開發經驗,有使用過mvvm架構經驗的開發者來說,入門小程式也就是幾個小時的時間本人就是。既然花個幾個小時能夠入門小程式原生開發,為何還要去選那些坑較多,入門時間相同的架構呢。。。

是以制定了如下開發原則:

盡量采用微信提供的原生元件,減少引入外部元件,手撸項目中所需的自定義元件,全局存儲資料。頁面間采用觀察者模式進行通信

觀察者模式

正常的觀察者模式實作起來并不複雜,總結來說就是:訂閱器中存儲了所有訂閱者注冊的所有回調函數,當事件發生時,訂閱器就會循環周遊所有的訂閱者,并找出訂閱該事件的訂閱者所注冊的回調函數并執行;取消訂閱則是重訂閱者數組中删除對應的回調函數。

結合在小程式中使用就是在一開始初始化登入元件時就初始化消息訂閱器,并将其儲存在全局資料(globalData)中,這樣全局就駐留了該對象,在各個頁面中就可以輕松調用訂閱器中的訂閱、釋出方法來實作通信了。

// 訂閱function _bind(eventName, callback, isOne, context) {  /* eslint valid-typeof: 0 */
  if (typeof eventName !== 'string' || typeof callback !== 'function') {    throw new Error('args: ' + stringStr + ', ' + functionStr + '')
  }  if (!hasOwnKey(__onfireEvents, eventName)) {
    __onfireEvents[eventName] = {}
  }
  __onfireEvents[eventName][++__cnt] = [callback, isOne, context]  return [eventName, __cnt]
}// 釋出function fire(eventName) {  // fire events
  var args = slice(arguments, 1)
  setTimeout(function() {    if (hasOwnKey(__onfireEvents, eventName)) {
    _each(__onfireEvents[eventName], function(key, item) {
      item[0].apply(item[2], args) // do the function
      if (item[1]) delete __onfireEvents[eventName][key] // when is one, delete it after triggle
    })
  }
  })
}      

上面是整個觀察者模式的核心:訂閱、釋出,當然如果你還想繼續完善,可以嘗試增加命名空間來防止事件名沖突以及增加離線事件支援。

自定義元件

微信小程式自定義元件比較簡單,詳情可以檢視。這裡就以聊天界面中使用的自定義emoji元件舉例,來闡述如何實作一個自定義元件。

元件的定義方式,以及對應的生命周期鈎子這邊就不再說明,請查閱上面文檔。本元件借助了小程式提供的swiper元件(省的自己判斷scroll的位置來切換頁面),每個swiper-Item裡面再通過模闆循環出每張emoji圖檔,而每個emoji的key對應線上位址則由自己提前準備好的一個已經抽象好的js提供,每組swiper内含有的emoji數量則通過程式自動分割,并在最後添加删除按鈕。

// 為每頁配置設定對應的emojifunction splitAlbumKeys(arr, space, currentAlbum) {  const delta = space || 23
  let result = []  let factor = Math.ceil(arr.length / delta)  let begin = 0
  let end = 1
  if (factor == 1) {
    result = [[...arr]]
  } else {    for (let i = 1; i < factor; i++) {      let temp = []
      temp = [...arr.slice(begin, i * delta)]
      begin = i * delta
      result.push(temp)
    }
    result.push([...arr.slice(delta * (factor - 1), arr.length)])
  }  if (currentAlbum == 'emoji' || this.data.currentAlbum == 'emoji') { // 隻有emoji才添加删除按鈕
    result.map((cata, index) => {      if(index != (result.length-1)) {
        cata.push('[删除]')
      }
    })
  }  return result
}// 單擊emoji,向外傳遞事件function emojiTap(e) {  let emoji = e.target.dataset.emoji  if (!emoji) return
  this.triggerEvent("EmojiClick", emoji)
}// 單擊發送,向外傳遞事件function emojiSend () {    this.triggerEvent("EmojiSend")
}      

在外部使用過程中,隻需要監聽對應的事件即可.

<component-emoji bind:EmojiClick="emojiCLick" bind:EmojiSend="emojiSend"></component-emoji>      

遇到的一些坑

微信小程式開發可以說是在坑中前行,經常會遇到一些很奇怪的問題,在此記錄在冊,希望後來人可以跳過,增加開發效率

  • 小程式模闆引擎的清單循環支援數組,不支援對象
  • text 元件實質是行内标簽
  • background-image 隻能用網絡url或者base64
  • 注意事件對象中target 和 currentTarget的差別
  • URL 傳參數時微信會自動攔截'=',導緻後面頁面onLoad中options參數容易解析出錯
  • 二級頁面無法再使用tabbar,必須自定義
  • 自定義元件中methods對象中定義的方法必須使用es5的函數定義
  • 注意多種小程式授權情況
    • 直接同意
    • 拒絕後,進引導,繼續拒絕
    • 拒絕後,進引導,點選授權,進入授權設定頁,再點授權
    • 拒絕後,進引導,點選授權,進入授權設定頁,直接退出
  • input元件渲染級别提升為原生,某些布局下會出問題
  • 注意某些提升為原生元件(例如video、map等)導緻的層級問題
  • 當然還有一些微信提供的createSelectorQuery的一些問題,隻能等待大家去探索了

這邊僅僅是抛出一些我開發中遇到的部分問題,在整個開發過程中踩過的坑遠遠不止這些,希望我們一起在坑中前行。。。

寫在最後

初步統計本項目大概8500行代碼(去除IM SDK以及MD5加密庫),換句話說不到9000行代碼,你就能在微信中實作一個mini 微信,這一切均借助于雲信IM SDK強大的即時通訊能力。當然本期版本還存在很多不足的地方,希望在做第二期群聊功能的時候,能繼續更新整體的組織架構。

網易雲信IM免費試用請點選這裡。

更多網易技術、産品、營運經驗分享請通路網易雲社群

相關文章:

【推薦】 Spring Boot 學習系列(序)—Spring Boot

【推薦】 使用 typescript ,提升 vue 項目的開發體驗(1)