微信小程序頁面間通訊的5種方式

微信小程序頁面間通的5種方式

PageModel(頁面模型)對小程序而言是很重要的一個概念,從app.json中也能夠看到,小程序就是由一個個頁面組成的。git

6d54693.png

如上圖,這是一個常見結構的小程序:首頁是一個雙Tab框架PageA和PageB,子頁面pageB, PageC。github

讓咱們假設這樣一個場景:首頁PageA有一個飄數,當咱們從PageA新開PageC後,作一些操做,再回退到PageA的時候,這個飄數要刷新。很顯然,這須要在PageC中作操做時,能通知到PageA,以便PageA作相應的聯動變化。json

這裏的通知,專業點說就是頁面通訊。所謂通訊,u3認爲要知足下面兩個條件:redux

  1. 激活對方的一個方法調用
  2. 可以向被激活的方法傳遞數據

本文將根據項目實踐,結合小程序自身特色,就小程序頁面間通訊方式做一個探討與小結。小程序

通訊分類

按頁面層級(或展現路徑)能夠分爲:微信小程序

  1. 兄弟頁面間通訊。如多Tab頁面間通訊,PageA,PageB之間通訊
  2. 父路徑頁面向子路徑頁面通訊,如PageA向PageC通訊
  3. 子路徑頁面向父路徑頁面通訊,如PageC向PageA通訊

按通訊時激活對方方法時機,又能夠分爲:緩存

  1. 延遲激活,即我在PageC作完操做,等返回到PageA再激活PageA的方法調用
  2. 當即激活,即我在PageC作完操做,在PageC激活PageA的方法調用

方式一: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可能出現讀寫失敗,從面形成通訊失敗
注意點:頁面初始化時也會觸發onShowapp

方式二:onShow/onHide + 小程序globalData

同方式一同樣,利用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,經過訂閱發佈實現通訊。在發佈事件時,激活對方方法,同時傳入參數,執行事件的訂閱方法

ab915ab.gif

/* /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呢。其它很簡單,每一個頁面有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屬性,可能會有一些風險

以上!

原文地址: http://www.u3xyz.com/#/detail/20

相關文章
相關標籤/搜索