本文由做者鄒永勝受權網易雲社區發佈。css
爲了更好的展現咱們即時通信SDK強悍的能力,網易雲信IM SDK微信小程序DEMO的開發就提上了日程。用產品的話說就是:html
雲信 IM 小程序 SDK 的能力演示前端
提供開發者小程序開發參考vue
換句話說就是在微信裏面經過咱們雲信的IM SDK再實現一個mini版微信。整個小程序主要功能點總的來講是:android
登陸註冊(爲了實現不一樣端同一帳號體系,因此沒有采用微信受權登陸)webpack
最近會話展現ios
通信錄web
單聊對話vuex
用戶名片typescript
廢話很少說直接上圖:
一期已經上線,不足的地方,懇請斧正
本文從基礎開始介紹在開發雲信DEMO的過程當中的一些難點、總體的結構設計、思考的一些解決方案以及踩過的一些坑,但願對你們有些幫助固然但願更多人接入網易雲信SDK。
小程序開發基本零門檻,難度基本與模板語言至關,若是你有使用MVVM框架開發前端的經驗,基本花個半小時過一遍微信小程序官方文檔,便可入門,具體開發細節能夠邊作邊查本人就是這樣的。。。。
首先須要明白小程序的運行環境,它運行在微信的上下文中,處於微信這個沙盒中,沒有window對象,不能訪問基於browser context下的DOM。在ios設備上是運行在JSCore(蘋果開發),在android設備上則是在X5(騰訊基於webkit開發),在開發工具中運行在nwjs(同類型還有electron)
一個標準的小程序是由一個應用實例以及多個頁面實例構成。仔細想來微信小程序不就是由多個相互關聯的頁面組成的嘛,在每一個頁面中,須要考慮與外部以及與其餘頁面進行交互。本着「3W+1H」原則,所以也就能夠提煉出在開發整個IM DEMO過程當中須要關注的點:
如何定義頁面、修改樣式
頁面怎麼進行屏幕適配
多頁面間怎麼進行通訊
每一個頁面的生命週期過程
如何定義組件、組件間如何通訊
局部與全局狀態的通訊
交互事件的處理
官方提供的一些組件以及能力
在微信官網下載開發工具,而後新建一個小程序工程,會發現項目根目錄下會有一個 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結構以及樣式時的解決方案。
整個小程序是由多個頁面組成,有時候會遇到須要跨頁面進行通訊的場景,例如聊天跳轉到聊天界面、刪除、拉黑好友後通知外部進行好友列表的刷新等等。思考後有以下幾種方式可供參考:
頁面跳轉狀況下能夠經過querystring進行數據的傳遞。缺點是數據量受到querystring大小的限制並且僅僅侷限於頁面間跳轉。
全局狀態下存儲,每次變化後修改全局數據(localStorage或globalData),而後頁面每次onShow時檢查此全局數據,並作出相應的反應。缺點是顯而易見的,業務複雜時,冗餘代碼十分多,且須要觸發onShow方法,存在必定的侷限性。
要想知足耦合性小、不侷限於頁面跳轉通訊、通訊數據量不受限制這些需求,很明顯發佈/訂閱模式(觀察者模式)
符合咱們的要求。既能作到時間上解耦又能作到對象間解耦。iOS端的Notification Center以及android端的EventBus都是經過這一設計模式來處理跨頁面間通訊的的需求的。而後微信小程序內部並無集成這一事件通知機制,所以須要手動去實現一個並將其與微信小程序的頁面生命週期結合起來。
觀察者模式是由調度中心、發佈者、訂閱者組成。訂閱者會先在調度中心訂閱某一特定事件並註冊對應的回調函數,當發佈者發佈了該事件後,訂閱中心就會取出訂閱了該事件的全部訂閱者註冊的回調函數進行執行。
觀察者模式不難實現,重點是如何在微信小程序中搭配其特有的生命週期來使用。本項目在用戶登陸以及註冊成功時會初始化消息訂閱中心,並全局(globalData)保存,使得訂閱器一直駐存在內存中,調用時直接從globalData調用便可。固然這其中還存在一些小問題,在頁面進行切換時須要注意訂閱者、發佈者之間的時序,好比訂閱早於發佈或者發佈以後還未訂閱的狀況。後期會詳細介紹該種模式的實現過程敬請期待。
傳統的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則視狀況而定)
使用頁面狀態數據或者全局數據
從小程序基礎庫版本 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開發規範 | vue開發規範 |
狀態管理 | 無 | 無 | vuex |
組件化 | 比較原始 | 自定義組件規範 | vue組件 |
多端複用 | 不可 | 可轉化爲H5 | 可轉化爲H5 |
構建方式 | 開發工具內置自動構建 | 框架內置 | webpack |
構建原理 | 開發工具自動構建 | 構建爲dist後轉化爲小程序支持類型而後將開發工具指向dist目錄,支持熱更新 | 構建爲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)