在做小程序項(xiàng)目的時(shí)候不難發(fā)現(xiàn),使用navigateTo進(jìn)行頁面跳轉(zhuǎn)后,點(diǎn)擊左上角或使用navigateBack返回,總是會(huì)按照之前的頁面進(jìn)入倒序來展示頁面,那么問題來了,它們的跳轉(zhuǎn)規(guī)則是什么樣的呢?結(jié)合到實(shí)際業(yè)務(wù)中如何靈活運(yùn)用呢?
什么是頁面棧?
首先先來了解一下小程序的運(yùn)行環(huán)境: 小程序的運(yùn)行環(huán)境分成渲染層和邏輯層,其中 WXML 模板和 WXSS 樣式工作在渲染層,JS 腳本工作在邏輯層。 小程序的渲染層和邏輯層分別由2個(gè)線程管理:渲染層的界面使用了WebView 進(jìn)行渲染;邏輯層采用JsCore線程運(yùn)行JS腳本。一個(gè)小程序存在多個(gè)界面,所以渲染層存在多個(gè)WebView線程,這兩個(gè)線程的通信會(huì)經(jīng)由客戶端做中轉(zhuǎn),邏輯層發(fā)送網(wǎng)絡(luò)請(qǐng)求也經(jīng)由Native轉(zhuǎn)發(fā),小程序的通信模型下圖所示。

我們可以看到,一個(gè)頁面使用一個(gè) WebView 線程進(jìn)行渲染。如果打開10個(gè)頁面,則會(huì)開啟 10 個(gè) WebView 線程,此時(shí)內(nèi)存中的十個(gè)webView線程我們稱之為頁面棧。當(dāng)然小程序也會(huì)對(duì)這塊內(nèi)存做限制,目前頁面棧的限制是不能超過十條。在小程序中頁面的路由是小程序框架本身控制的我們不要去手動(dòng)管理, 小程序框架通過一個(gè)頁面棧的設(shè)計(jì)來管理所有的界面,當(dāng)發(fā)生路由跳轉(zhuǎn)時(shí),頁面棧就會(huì)做出相應(yīng)的變化,在小程序頁面中通過 getCurrentPages() 就可以獲取到當(dāng)前的頁面棧。
舉個(gè)栗子: 在父頁面中先獲取頁面棧:
const page = getCurrentPages(); // 父頁面 console.log('父頁面', page); //父頁面 復(fù)制代碼
通過wx.navigateTo跳轉(zhuǎn)子頁面,在子頁面中再獲取頁面棧:
const page = getCurrentPages(); // 子頁面 console.log('子頁面', page); //子頁面 復(fù)制代碼
輸出:

通過上面的例子可以看到,我們可以在頁面中通過 getCurrentPages() 方法來獲取當(dāng)前頁面棧,并且獲取到的是一個(gè)數(shù)組,其中每個(gè)item都是每個(gè)頁面的Page對(duì)象(也就是在頁面中的this對(duì)象),由此我們引發(fā)一些思考……
路由跳轉(zhuǎn)時(shí)頁面棧表現(xiàn)?
當(dāng)發(fā)生路由切換的時(shí)候,頁面棧的表現(xiàn)如下: 當(dāng)發(fā)生路由切換的時(shí)候,頁面棧的表現(xiàn)如下:
情景頁面棧表現(xiàn)對(duì)應(yīng)路由跳轉(zhuǎn)API小程序初始化新頁面入棧打開新頁面新頁面入棧wx.navigateTo 或使用組件頁面重定向當(dāng)前頁面出棧,新頁面入棧wx.redirectTo 或使用組件頁面返回頁面不斷出棧,直到目標(biāo)頁wx.navigateBack 或使用組件或用戶按左上角返回按鈕Tab 切換頁面全部出棧,只留下新的 Tab 頁面wx.switchTab 或使用組件 或用戶切換 Tab重加載頁面全部出棧,只留下新的頁面wx.reLaunch 或使用組件
我們?cè)谧鲰?xiàng)目的時(shí)候,巧妙運(yùn)用路由跳轉(zhuǎn)和頁面棧會(huì)節(jié)省很多代碼,用戶體驗(yàn)也會(huì)得到相應(yīng)的提升,所以,在開始項(xiàng)目之前,定好頁面跳轉(zhuǎn)規(guī)則相當(dāng)重要。
頁面棧的實(shí)際運(yùn)用分析
下面我們分析一下頁面棧的變化過程,從分析中,我們需要明白的一個(gè)重要問題就是,當(dāng)客戶按返回按鈕的時(shí)候究竟會(huì)跳轉(zhuǎn)到那個(gè)界面,這是我們分析頁面棧變化的的意義。 首先我們?cè)陧撁嬷姓{(diào)用兩次navigateTo,頁面棧情況如下

這時(shí)顯示的界面是pageC ,如果客戶在此時(shí)返回則會(huì)一切正常,回退的第一個(gè)界面是pageB,然后是pageA。但是如果在pageC 界面調(diào)用 wx.redirectTo({url:'pageD'}) 則情況就會(huì)不一樣,我們先看一下跳轉(zhuǎn)到pageD后頁面棧的情況如何。

根據(jù)棧的情況,我們可以分析出。如果使用 wx.redirectTo跳轉(zhuǎn)到pageD頁面,然后在回退的時(shí)候是不能再次回退到pageC的,而會(huì)直接回退到pageB。 通過上面對(duì)頁面棧的分析,我們可以看到棧的變化是會(huì)影響客戶回退頁面的順序的,所以根據(jù)自己的需求合理的使用不同的跳轉(zhuǎn)方法是非常重要的。如果使用不當(dāng)就會(huì)導(dǎo)致跳轉(zhuǎn)混亂讓人摸不清頭腦 下面分析一種調(diào)轉(zhuǎn)重復(fù)頁面的情況:

如圖所示棧中出現(xiàn)了兩個(gè)相同的pageB界面,這個(gè)時(shí)候如果用戶按退出鍵就會(huì)出現(xiàn)一個(gè)頁面出現(xiàn)2次的情況,而且有一個(gè)界面的數(shù)據(jù)也是舊的數(shù)據(jù)。因此為了避免這個(gè)問題,我們應(yīng)該在 PageC 頁面避免將 PageB重復(fù)壓入棧中,所以在pageC頁面使用wx.navigateBack({delta:1}); 進(jìn)行頁面回退。而數(shù)據(jù)刷新的問題則在頁面的onShow函數(shù)中進(jìn)行即可。
情景:確認(rèn)訂單頁用戶點(diǎn)擊左上角返回
假設(shè)場(chǎng)景:用戶在商品詳情頁直接點(diǎn)擊“立即購(gòu)買”下單購(gòu)買,進(jìn)入確認(rèn)訂單頁,付款成功后跳轉(zhuǎn)到付款成功頁面,此時(shí)用戶點(diǎn)擊左上角箭頭進(jìn)行了返回…… 處理:此時(shí)理應(yīng)進(jìn)入商品詳情頁,所以在確認(rèn)訂單頁付款成功跳轉(zhuǎn)時(shí)應(yīng)當(dāng)將確認(rèn)訂單頁出棧,新頁面入棧,那么就不可以使用wx.navigateTo來進(jìn)行頁面跳轉(zhuǎn),應(yīng)當(dāng)使用wx.redirectTo

情景:確認(rèn)訂單頁用戶選擇已有收貨人
假設(shè)場(chǎng)景:在確認(rèn)訂單頁,用戶需要選擇已有的收貨人,而已有收貨人列表在另一個(gè)頁面,那么用戶點(diǎn)擊“選擇收貨人”之后,使用wx.navigateTo跳轉(zhuǎn)到收貨人列表,點(diǎn)擊某個(gè)收貨人,帶參數(shù)返回確認(rèn)訂單頁…… 處理:在確認(rèn)訂單頁使用wx.navigateTo跳轉(zhuǎn)到收貨人列表,然后在收貨人列表里click事件中獲取頁面棧,直接往上一個(gè)頁面setData,然后退回上一個(gè)頁面,show code:
const page = getCurrentPages() if (page.length > 1) { page[page.length - 2].setData({ 收貨人: 選中的某個(gè)收貨人詳情 //[object] }) wx.navigateBack({ delta: 1 }) } 復(fù)制代碼
上面例子中提到過,在頁面中通過 getCurrentPages() 方法來獲取當(dāng)前頁面棧,并且獲取到的是一個(gè)數(shù)組,其中每個(gè)item都是每個(gè)頁面的Page對(duì)象,那么我們就可以使用 setData 方法直接改變上一個(gè)頁面展示的數(shù)據(jù),并且直接退回上一個(gè)頁面。
此時(shí)官方提醒:

雖然這種方法簡(jiǎn)便,但是官方也給出提醒,頁面棧數(shù)據(jù)可以自行修改,但是!一定要慎重,否則會(huì)導(dǎo)致頁面狀態(tài)錯(cuò)誤。
總結(jié): 總覺得漏了點(diǎn)啥,又想不起來…… 官方文檔應(yīng)有盡有,多研究官方文檔,多引發(fā)思考并手寫demo嘗試,總會(huì)有一些新的發(fā)現(xiàn),另外,方法千萬條,隨便選一條,根據(jù)自己業(yè)務(wù)邏輯選用合適的方法。