淺析Ionic頁面傳參方案
Ionic3 中父頁面給子頁面傳遞引數非常容易,使用框架提供的 navParams 來實現就行了,但是反過來子頁面給父頁面傳遞就沒那麼容易了,因為在路由的 navController 裡面的 pop 函式並不支援傳引數,使得前面的方法沒有用了(表面上是,實際上仍然有用,詳細看正文),這個時候就需要別的手段來達到我們的目的了,故本文主要將對 Ionic3 中子頁面向父頁面傳參問題提供筆者常用的幾種解決方案,若有問題或更多建議,歡迎各位同僚下方評論,共同進步!
簡介
在 Ionic 中如何做到頁面與頁面間的通訊?這裡給出三種方向: 路由傳參,事件傳參,service 傳參 。由於三個方向各自又有不同的實現,且每種方案適應的場景都不一樣,下面就會從實現,優缺點,適應場景這幾個方面來比較一下這幾個方案,最後各位看官老爺們各取所需。
一、利用路由傳遞引數
假如你還不知道Ionic3的路由傳值的話,請戳NavController 瞭解一下。
好了正文開始,這裡要用的就是navParams,前面已經說了,在頁面的pop操作裡面是不允許傳值的,那如何做到用navParams傳值。這裡給出兩種方案
1. 傳遞“頁面“
// 本文出現navCtrl都指代navContronller // 語法不一定正確,請根據自身程式碼進行修正 <!--父頁面--> public a = 'a'; navCtrl.push('childrenPage', { parent: this }) <!--子頁面--> parent = navParams.get('parent'); parent.a = 'change form childe'; <!--父頁面--> console.log(a) // change form childe // 你也可以直接呼叫父頁面的方法 複製程式碼
為什麼可以這樣用?
歷史棧
Ionic3 的路由有個歷史棧的概念,相信知道棧的同學肯定都很熟悉這個,其實就是和棧一樣的操作,app開始在根頁面的時候,這個歷史棧只有1個頁面,就是你的根頁面,之後每當使用navController的push方法到一個新的頁面的時候,歷史棧把新的頁面加入進來,pop的時候就把那個頁面從歷史棧中彈出。
路由
Ionic3 的路由機制比較“神奇”,和別的常見的Vue-Router或Angular單頁面路由方式不太一樣,有點像偽路由。因為 歷史棧內的頁面全部都真實的存在於當前頁面上 ,嘻嘻,證據如下圖:

可以看到,我的頁面實際上經過了HomePage —> parentPage —> childPage跳轉,現在在childPage了,也就是說歷史棧內有這三個頁面,而在瀏覽器內可以看到三個頁面都真實的存在。
分析
而檢視頁面程式碼可以知道,每個頁面不過是被 InoicPage 裝飾器修飾過的 Angular 的元件而已,那麼就可以通過獲取元件的例項來改變元件的狀態。結合上面路由的情況我們知道:即使到了子頁面,我們的父頁面或者說父元件沒有被destroy的(可自行驗證)。所以在子頁面中,我們是可以通過拿到歷史頁面的例項來對其進行操作的。最後我們再整理一下思路:這裡利用navParams來傳遞父頁面本身到子頁面中,然後子頁面獲取到父頁面來對其進行操作,通過這個方式來達到傳遞引數的目的。
優點
使用方式簡單高效,甚至可以規避傳遞引數這一個步驟直接在子頁面對父頁面進行操作(不推薦)。
缺點
- 父子頁面之間耦合度較高,子頁面的操作對於父頁面來說是“隱形”的,使用過度會造成維護的困難,因為你會需要時時刻刻關注子頁面對父頁面的影響。
- 多層級的頁面傳參需要經過中間多個頁面的傳遞,即使他們或許用不到這個父頁面,會導致多個頁面傳遞多個不需要的引數,程式碼會變得臃腫難以維護。
推薦使用場景
涉及對父頁面的操作少且不復雜的場景,且僅推薦父 —> 子間使用,而且希望在使用時宣告一個明確的引數名稱以表明父子頁面之間的聯絡,提升程式碼的可維護性。
2. 傳遞迴調
<!--這裡是網上常見版本,但筆者不太喜歡此版本--> <!--父頁面--> public a = 'a'; // 必須使用箭頭函式,若不使用,此法將會失效 public parentPromise = (childrenParams) => new Promis((resolve, reject) => { // 這裡可以做你想利用回傳的引數想做的事 this.a = childrenParams; console.log('childe change work: ', this.a); resolve(); }); navCtrl.push('childrenPage', { parentPromise: this.parentPromise }); <!--子頁面--> parentPromise = navParams.get('parentPromise'); parentPromise('change form childe').then(() => { // 此時會列印 childe change worke: change form childe navCtrl.pop(); }) 複製程式碼
預備知識
這裡程式碼稍微有點繞,需要你瞭解的知識有 Promise、箭頭函式、TypeScript ,假如你不太瞭解,請戳Promise、箭頭函式,瞭解過後再看接下來的內容。
分析
- parentPromise:
- 接受一個引數(一般由子頁面傳入),並返回一個 Promis
- Promise 內部收到引數後就可以利用這個引數執行一些操作,結束 Promis
- 子頁面的 parentPromise 執行順序:
- 傳入引數,在子頁面內執行來自父頁面的操作,執行完畢後再執行 pop 回到父頁面
注意是在子頁面執行 parentPromise 內的程式碼,程式碼之所以能夠跑成功,原因其實和上面第一種用法的原因是一樣的,可以說利用的原理都是一樣,但是和第一種略微不同的是:前者是 顯示的傳遞this給子頁面,控制權全部交給了子頁面 ,後者是 利用箭頭函式將 parentPromise 的this繫結在父頁面上,控制權在父頁面上 。
優點
- 相比之前的做法,此法降低了父頁面與子頁面的耦合,提升了程式碼的可維護性。
缺點
- 多層級無法高效的使用。
- 相比前一種方法,此法更難理解一點。
我想說
此段程式碼是有問題的。並不是結果有問題,而是程式碼的實際執行行為與程式碼表現的執行預期出現的差異的問題。筆者嘗試對 parentPromise 解讀時的預期是:子頁面傳入引數 —> 子頁面返回 —> 父頁面獲得引數然後做相關操作,但是實際執行行為如之前的分析根本不是這樣的:子頁面傳入引數 —> 獲得引數然後做相關操作 —> 子頁面返回,可以發現實際執行過程中 parentPromise 內部的 Promise 其實是沒有太多意義的,在子頁面完全可以直接回調然後執行 pop 的操作。這就是筆者對這段程式碼不太喜歡的原因。 這段下面給出筆者的“改進版”供讀者評論。
<!--父頁面--> public a = 'a'; // 不使用箭頭函式依舊可以使用 public popWithParams(childNavCtrl: navController, childeParams: any) { // 回到父頁面再操作 childNavCtrl.pop().then(function() { this.a = childrenParams; console.log('childe change work: ', this.a); } }; navCtrl.push('childrenPage', { popWithParams: this.popWithParams }); <!--子頁面--> popWithParams = navParams.get('parentPromise'); popWithParams(navCtrl, 'change form childe'); 複製程式碼
3. 利用this和回撥
<!--此法是前兩者的結合,和第二種方法更像,但是用法更簡單,也更容易理解--> <!--父頁面--> public a = 'a'; // 不使用箭頭函式依舊可以使用 public parentCallback = (childeParams: any) => { // this被繫結在父頁面 // 僅當此處程式碼為非同步時(例:資料需要ajax獲取),考慮法2傳遞Promis的用法 this.a = childrenParams; console.log('childe change work: ', this.a); }; navCtrl.push('childrenPage', { parentCallback: this.parentCallback }); <!--子頁面--> popWithParams = navParams.get('parentPromise'); parentCallback('change form childe'); navCtrl.pop() 複製程式碼
優點
使用簡單,容易理解,且父子頁面耦合也比較低控制權在父頁面。
缺點
同前兩者。
二、事件傳參
1. Ionic提供的Events
<!--父頁面--> constructor(public events: Events) {} ngOnInit() { // 訂閱來自子頁面的事件 events.subscribe('childParams:doWhenInit', (childParams) => { console.log(childParams); // params from childPage }); } <!--子頁面--> constructor(public events: Events) {} getParams() { this.events.publish('childParams:doWhenInit', 'params from childPage'); } 複製程式碼
預備知識
此為 Ionic 提供的事件支援,詳細請戳Ionic Events 。
優點
- 思路易理解,子頁面釋出一個事件,父頁面訂閱事件並處理。
- 父子頁面解耦。
- 多層級頁面傳遞引數方便。
缺點
- 事件名稱不易管理,應用複雜的情況下,很多事件名稱需要管理,造成維護上的困難。
- 訂閱方即父頁面需要手動取消訂閱,不然可能會訂閱多次。
- 跨頁面傳遞引數時,訂閱方即父頁面需要在事件釋出時已經生成並訂閱事件,才能收到引數。
2. RxJS 的 Subject,BehaviorSubject
3. Angular 的 EventEmitter
這兩種方法都需要搭配Service結合,思路和 Ionic 提供的 Events 類似,此處不多做研究了,感興趣的同學可以看看相關內容的內容。
三、Service
<!--pageParamsManagerService--> // 可在全應用範圍內被訪問及改變 public a = 'no change'; <!--父頁面--> import 'pageParamsManagerService' from './pageParamsManagerService' constructor(public ppmService: pageParamsManagerService) {} // 注意不可放在 onInit 或 ionViewDidLoad鉤子內,因為 push 到子頁面時父頁面並未銷燬, pop 回來時不會執行這兩個鉤子 ionViewWillEnter() { // 第一次進入:no change; // 子頁面pop進入:change from childePage console.log(this.ppmService.a); } <!--子頁面--> import 'pageParamsManagerService' from './pageParamsManagerService' constructor(public ppmService: pageParamsManagerService) { // 改變公共變數 this.ppmService.a = 'change from childePage'; } 複製程式碼
預備知識
Angular 應用裡有個Service 的概念,一般來說 Service 都是單例模式且在全應用級別作用域都可訪問(此處不做過多探討),故我們可以利用這個特性來達到我們的傳參目的。
優點
- 使用方便簡單。
- 頁面間完全解耦。
- 支援多頁面間訪問,且無頁面生成時間要求,多處頁面也完全解耦。
缺點
- 因為是單例模式且需要在app.module引入,需要做好模組管理,這裡又是一大坑,感興趣的同學可以自己瞭解一下。
- 若管理不慎會造成意外的情況,需要時刻注意是Service單例的。
- 若引數較少,操作不多的情況下,僅僅為了傳參引入一個service會顯得程式較為臃腫。
結語
我們從 路由傳參、事件傳參、service 傳參 三個方向給出了對應的傳參方案並分析了各自優劣和適用場景,筆者能力有限,有更多方案的同學歡迎在評論提出,若發現問題的,也希望不吝賜教,謝謝!!