小程式頁面間通的5種方式
PageModel(頁面模型)對小程式而言是很重要的一個概念,從app.json中也可以看到,小程式就是由一個個頁面組成的。
如上圖,這是一個常見結構的小程式:首頁是一個雙Tab框架PageA和PageB,子頁面pageC, PageD。
讓我們假設這樣一個場景:首頁PageA有一個飄數,當我們從PageA新開PageC後,做一些操作,再回退到PageA的時候,這個飄數要重新整理。很顯然,這需要在PageC中做操作時,能通知到PageA,以便PageA做相應的聯動變化。
這裡的通知,專業點說就是頁面通訊。所謂通訊,u3認為要滿足下面兩個條件:
1. 啟用對方的一個方法呼叫
2. 能夠向被啟用的方法傳遞資料
本文將根據專案實踐,結合小程式自身特點,就小程式頁面間通訊方式作一個探討與小結。
通訊分類
按頁面層級(或展示路徑)可以分為:
1. 兄弟頁面間通訊。如多Tab頁面間通訊,PageA,PageB之間通訊
2. 父路徑頁面向子路徑頁面通訊,如PageA向PageC通訊
3. 子路徑頁面向父路徑頁面通訊,如PageC向PageA通訊
按通訊時啟用對方方法時機,又可以分為:
1. 延遲啟用,即我在PageC做完操作,等返回到PageA再啟用PageA的方法呼叫
2. 立即啟用,即我在PageC做完操作,在PageC啟用PageA的方法呼叫
由於父路徑頁面向子路徑頁面通過url傳參基本可以滿足通訊需求,所以本文主要討論通訊場景是子路徑頁面向父路徑頁面通訊
方式一:onShow/onHide + localStorage
利用onShow/onHide啟用方法,通過localStorage傳遞資料。大概邏輯如下
// pageA let isInitSelfShow = true; Page({ data: { helloMsg: 'hello from PageA' }, onShow() { // 頁面初始化也會觸發onShow,這種情況可能不需要檢查通訊 if (isInitSelfShow) return; let newHello = wx.getStorageSync('__data'); if (newHello) { this.setData({ helloMsg: newHello }); // 清隊上次通訊資料 wx.clearStorageSync('__data'); } }, onHide() { isInitSelfShow = false; }, goC() { wx.navigateTo({ url: '/pages/c/c' }); } });
// pageC
Page({
doSomething() {
wx.setStorageSync('__data', 'hello from PageC');
}
});
優點:實現簡單,容易理解
缺點:如果完成通訊後,沒有即時清除通訊資料,可能會出現問題。另外因為依賴localStorage,而localStorage可能出現讀寫失敗,從面造成通訊失敗
注意點:頁面初始化時也會觸發onShow
方式二:onShow/onHide + 小程式globalData
同方式1一樣,利用onShow/onHide啟用方法,通過讀寫小程式globalData完成資料傳遞
// PageA
let isInitSelfShow = true;
let app = getApp();
Page({
data: {
helloMsg: 'hello from PageA'
},
onShow() {
if (isInitSelfShow) return;
let newHello = app.$$data.helloMsg;
if (newHello) {
this.setData({
helloMsg: newHello
});
// 清隊上次通訊資料
app.$$data.helloMsg = null;
}
},
onHide() {
isInitSelfShow = false;
},
goC() {
wx.navigateTo({
url: '/pages/c/c'
});
}
});
// PageC
let app = getApp();
Page({
doSomething() {
app.$$data.helloMsg = 'hello from pageC';
}
});
優點:實現簡單,實現理解。因為不讀寫localStorage,直接操作記憶體,所以相比方式1,速度更快,更可靠
缺點:同方式1一樣,要注意globalData汙染
方式三:eventBus(或者叫PubSub)方式
這種方式要先實現一個PubSub,通過訂閱釋出實現通訊。在釋出事件時,啟用對方方法,同時傳入引數,執行事件的訂閱方法
/* /plugins/pubsub.js
* 一個簡單的PubSub
*/
export default class PubSub {
constructor() {
this.PubSubCache = {
$uid: 0
};
}
on(type, handler) {
let cache = this.PubSubCache[type] || (this.PubSubCache[type] = {});
handler.$uid = handler.$uid || this.PubSubCache.$uid++;
cache[handler.$uid] = handler;
}
emit(type, ...param) {
let cache = this.PubSubCache[type],
key,
tmp;
if(!cache) return;
for(key in cache) {
tmp = cache[key];
cache[key].call(this, ...param);
}
}
off(type, handler) {
let counter = 0,
$type,
cache = this.PubSubCache[type];
if(handler == null) {
if(!cache) return true;
return !!this.PubSubCache[type] && (delete this.PubSubCache[type]);
} else {
!!this.PubSubCache[type] && (delete this.PubSubCache[type][handler.$uid]);
}
for($type in cache) {
counter++;
}
return !counter && (delete this.PubSubCache[type]);
}
}
//pageA
let app = getApp();
Page({
data: {
helloMsg: 'hello from PageA'
},
onLoad() {
app.pubSub.on('hello', (number) => {
this.setData({
helloMsg: 'hello times:' + number
});
});
},
goC() {
wx.navigateTo({
url: '/pages/c/c'
});
}
});
//pageC
let app = getApp();
let counter = 0;
Page({
doSomething() {
app.pubSub.emit('hello', ++counter);
},
off() {
app.pubSub.off('hello');
}
});
缺點:要非常注意重複繫結的問題
方式四:gloabelData watcher方式
前面提到方式中,我們有利用globalData完成通訊。現在資料繫結流行,結合redux單一store的思想,如果我們直接watch一個globalData,那麼要通訊,只需修改這個data值,通過water去啟用呼叫。同時修改的data值,本身就可以做為引數資料。
為了方便演示,這裡使用[ oba ]這個開源庫做為物件監控庫,有興趣的話,可以自己實現一個。
//pageA
import oba from '../../plugin/oba';
let app = getApp();
Page({
data: {
helloMsg: 'hello from PageA'
},
onLoad() {
oba(app.$$data, (prop, newvalue, oldValue) => {
this.setData({
helloMsg: 'hello times: ' + [prop, newvalue, oldValue].join('#')
});
});
},
goC() {
wx.navigateTo({
url: '/pages/c/c'
});
}
});
//pageC
let app = getApp();
let counter = 0;
Page({
doSomething() {
app.$$data.helloTimes = ++counter;
}
});
優點:資料驅動,單一資料來源,便於除錯
缺點:重複watch的問題還是存在,要想辦法避免
方式五:通過hack方法直接呼叫通訊頁面的方法
直接快取頁面PageModel, 通訊時,直接找到要通訊頁面的PageModel,進而可以訪問通訊頁面PageModel所有的屬性,方法。簡直不能太cool,感謝小組內小夥伴發現這麼amazing的方式。
有人肯定會問了,怎麼拿到這個所有的PageModel呢。其它很簡單,把頁面this(即些頁面PageModel)快取即可,快取時用頁面路徑作key,方便查詢。那麼頁面路徑怎麼獲取呢,答案就是page.__route__這個屬性
// plugin/pages.js
// 快取pageModel,一個簡要實現
export default class PM {
constructor() {
this.$$cache = {};
}
add(pageModel) {
let pagePath = this._getPageModelPath(pageModel);
this.$$cache[pagePath] = pageModel;
}
get(pagePath) {
return this.$$cache[pagePath];
}
delete(pageModel) {
try {
delete this.$$cache[this._getPageModelPath(pageModel)];
} catch (e) {
}
}
_getPageModelPath(page) {
// 關鍵點
return page.__route__;
}
}
// pageA
let app = getApp();
Page({
data: {
helloMsg: 'hello from PageA'
},
onLoad() {
app.pages.add(this);
},
goC() {
wx.navigateTo({
url: '/pages/c/c'
});
},
sayHello(msg) {
this.setData({
helloMsg: msg
});
}
});
/pageC
let app = getApp();
Page({
doSomething() {
// 見證奇蹟的時刻
app.pages.get('pages/a/a').sayHello('hello u3xyz.com');
}
});
優點:一針見血,功能強大,可以向要通訊頁面做你想做的任何事。無需要繫結,訂閱,所以也就不存在重複的情況
缺點:使用了__route__這個hack屬性,可能會有一些風險