PageModel(頁面模型)對小程序而言是很重要的一個概念,從app.json中也能夠看到,小程序就是由一個個頁面組成的。git
如上圖,這是一個常見結構的小程序:首頁是一個雙Tab框架PageA和PageB,子頁面pageB, PageC。github
讓咱們假設這樣一個場景:首頁PageA有一個飄數,當咱們從PageA新開PageC後,作一些操做,再回退到PageA的時候,這個飄數要刷新。很顯然,這須要在PageC中作操做時,能通知到PageA,以便PageA作相應的聯動變化。json
這裏的通知,專業點說就是頁面通訊。所謂通訊,u3認爲要知足下面兩個條件:redux
本文將根據項目實踐,結合小程序自身特色,就小程序頁面間通訊方式做一個探討與小結。小程序
按頁面層級(或展現路徑)能夠分爲:微信小程序
按通訊時激活對方方法時機,又能夠分爲:緩存
利用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可能出現讀寫失敗,從面形成通訊失敗
注意點:頁面初始化時也會觸發onShowapp
同方式一同樣,利用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污染
這種方式要先實現一個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'); } });
缺點:要很是注意重複綁定的問題
前面提到方式中,咱們有利用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的問題仍是存在,要想辦法避免
直接緩存頁面PageModel, 通訊時,直接找到要通訊頁面的PageModel,進而能夠訪問通訊頁面PageModel全部的屬性,方法。簡直不能太cool,感謝小組內小夥伴發現這麼amazing的方式。
有人確定會問了,怎麼拿到這個全部的PageModel呢。其它很簡單,每一個頁面有onLoad方法,咱們在這個事件中,把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屬性,可能會有一些風險
以上!