摘要:傳統的網頁編程采用的三劍客來實現,在微信小程序中同樣有三劍客。觀察者模式不難實現,重點是如何在微信小程序中搭配其特有的生命周期來使用。交互事件傳統的事件傳遞類型有冒泡型與捕獲型,微信小程序中自然也有。
本文由作者鄒永勝授權網易云社區發布。
簡介
為了更好的展示我們即時通訊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 引入組件并傳入屬性以及監聽事件
內部slot // 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] = {}
}
__onfireEventseventName = [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")
}
在外部使用過程中,只需要監聽對應的事件即可.
遇到的一些坑
微信小程序開發可以說是在坑中前行,經常會遇到一些很奇怪的問題,在此記錄在冊,希望后來人可以跳過,增加開發效率
小程序模板引擎的列表循環支持數組,不支持對象
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免費試用請點擊這里。
更多網易技術、產品、運營經驗分享請訪問網易云社區
文章來源: 網易云社區
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25329.html
摘要:但對于整個事件流上的別的元素來說,執行順序還會受到另外一個因素的影響。以上面的場景為例,在捕獲階段執行的事件,如果執行,則事件流終止,不會到達目標階段,的世界則不會被執行執行結果為線上參考事件流 向dom綁定事件的事件的三種方式 行內綁定 按鈕 js內綁定 btnDom.onclick = function clickHandler() { console.log(click)...
摘要:前端渲染過程的二三事本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。那么現在我們可以明白這個問題的關鍵所在了,因為在大部分頁面中是擁有的,而由于其解析順序,那么在事件之前必定已經成功構造樹。 前端渲染過程的二三事 本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。(文章地址一(系列),文章地址二) 希望大家在閱讀這篇文章之前能將上述...
摘要:分表字段的選擇。問題產生之前提到在分表應用上線前我們需要將原有表的數據遷移到新表中,這樣才能保證業務不受影響。雖說凌晨的業務量下降,但依然有少部分的請求過來,也會出現各種數據庫異常。 showImg(https://segmentfault.com/img/remote/1460000019462791?w=496&h=285); 前言 本篇是上一篇《一次分表踩坑實踐的探討》,所以還沒...
閱讀 3200·2021-11-25 09:43
閱讀 3206·2021-11-23 09:51
閱讀 3518·2019-08-30 13:08
閱讀 1568·2019-08-29 12:48
閱讀 3594·2019-08-29 12:26
閱讀 396·2019-08-28 18:16
閱讀 2561·2019-08-26 13:45
閱讀 2428·2019-08-26 12:15