歡迎來到我博客閱讀: BlueSun - 微信小程序路由實戰
4. 落地中轉策略 — LandTransfer 模塊javascript
5. 更好的開發體驗html
在微信小程序由一個 App()
實例,和衆多Page()
組成。而在小程序中全部頁面的路由所有由框架進行管理,框架以棧的形式維護了全部頁面,而後提供瞭如下 API 來進行路由之間的跳轉:前端
wx.navigateTo
wx.redirectTo
wx.navigateBack
wx.switchTab
wx.reLaunch
可是,對於一個企業應用,把這些問題留給了開發者:vue
Callback
的函數實現形式,與咱們現代廣泛的 Promise
和 async/await
存在 gap。wx.navigateTo
失效,須要開發者判斷使用 wx.redirectTo
或其餘APIwx.switchTab
才能跳轉。須要開發者主動判斷,不方便後期改動 Tab 頁面屬性。而本文,指望能對這若干問題,逐個提供解決方案。java
在這裏咱們一塊兒解決:git
咱們的思路是,但願能設計一種邏輯,根據場景來自動判斷使用哪一個微信路由 API,而後對外只提供一個函數,例如:github
gotoPage('/pages/goods/index')
具體邏輯以下:vue-router
wx.switchTab
。wx.navigateBack({ delta: X })
出棧到目標頁面。wx.redirectTo
替換棧頂頁面。wx.navigateTo
順帶的,咱們把這個函數以 Promise 形式實現,以及支持參數做爲 object
傳入,例如:typescript
gotoPage('/pages/goods/index', { name: 'jc' }).then(...).catch(...);
大部分場景下,只要使用gotoPage
就能知足。數據庫
那確定也會有特定的狀況,須要顯式的指定使用 navigateTo/switchTab/redirectTo/navigateBack
的哪個。
那麼咱們也按照相似的實現,知足相同模式的 API
navigateTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); switchTab('/pages/goods/index', { name: 'jc' }).then(...).catch(...); redirectTo('/pages/goods/index', { name: 'jc' }).then(...).catch(...); navigateBack('/pages/goods/index', { name: 'jc' }).then(...).catch(...);
這些函數均可之內聚到同一個模塊,咱們稱其爲:Navigator
const navigator = new Navigator(); navigator.gotoPage(...); navigator.navigateTo(...); navigator.switchTab(...); navigator.redirectTo(...); navigator.navigateBack(...);
模塊設計:
在這裏,咱們解決:
在許多應用開發中,咱們常常須要把某種模式匹配到的全部路由,全都映射到同個頁面中去。
例如,咱們有一個 Goods 頁面,對於全部 ID 各不相同的商品,都要使用這個頁面來承載。
那麼在代碼層面上,指望能實現這樣的調用方式:
// 建立路由實例 const router = new Router(); // 註冊路由 router.register({ path: '/goods/:id', // 虛擬路由 route: '/pages/goods/index', // 真實路由 }); // 跳轉到 /pages/goods/index,參數: onLoad(options) 的 options = { id: '123' } router.gotoPage('/goods/123'); // 跳轉到 /pages/goods/index,參數: onLoad(options) 的 options = { id: '456' } router.gotoPage('/goods/456');
Class Router 的核心邏輯是完成:
對於「路由的註冊」,咱們在其內部存儲一個 map 就能完成。
而對於「路徑的轉換」, vue-router
有相似的實現,經過其源碼發現,內部是使用 path-to-regexp 做爲路徑匹配引擎,咱們能夠拿來用之。
而後對於「路由的跳轉」,咱們能夠直接複用上面提到的 Navigator 模塊,經過輸入真實路徑,來完成路由的跳轉。
模塊設計:
其中:
在這裏,咱們解決:
咱們把如:掃小程序碼、公衆號菜單、公衆號文章等方式打開小程序某個頁面的路徑稱爲「外部路由」。
根據小程序的設計,暴露給外部的鏈接是真實的頁面路徑,如:/pages/home/index
,該設計在實踐中存在的弊端:各個落地頁分散,後期修改真實文件路徑難度大。
在 「中長生命週期」 產品中,隨着產品的迭代,咱們不免會遇到項目的重構。若是分發出去的都是沒通過處理的真實路徑的話,咱們重構時就會束手束腳,要作不少的兼容操做。由於你不知道,分發出去的小程序二維碼, 有多少被打印到實體物料中。
那麼,「虛擬路由」+「落地中轉」 的策略就顯得基本且重要了。
「虛擬路由」的功能,Router 模塊給咱們提供了支持了,咱們還須要對外提供一個統一的落地頁面,讓它來完成對內部路由的中轉。
基本邏輯:
$LAND_PAGE: /pages/land-page/index
path=/user&name=jc&age=18
在代碼層面上,咱們但願能實現這樣的使用:
// /pages/land-page/index.ts const landTransfer = new LandTransfer(landTransferOptions); Page({ onLoad(options) { landTransfer .run(options) .then(() => {...}) .catch(() => {...}); } });
而後針對 TS,咱們還可使用裝飾器版本,更加簡便:
import { landTransferDecorator } from 'wxapp-router'; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, });
微信小程序主要提供了兩個接口去生成小程序碼:
第一種方式,wxacode.get
數量限制爲 10w 個,雖然量很大了,絕大多數的小程序可能用不到這個量。
但若是咱們運營的是一箇中大型電商小程序的話,假如:1w 種商品 x 10 種商品規格,那就會超過這個數量。到時候再進行改造,就困難了。
因此,若是抱着是運營一個 「中長生命週期」 的產品的話,咱們會使用第二種方式:wxacode.getUnlimited
不盡人意的是,雖然它沒有數量限制,可是對參數會有 32 個字符的限制,顯然是不夠用的(一個 uuid 就 32 字符了)。
對於這種狀況,咱們可使用「短鏈參數」的形式解決,因爲wxacode.getUnlimited 會經過 scene
字段做爲 query 參數傳遞給小程序的,那麼咱們能夠經過 scene
參數來實現短鏈服務,這須要後端配合。
先後端交互以下:
/api/encodeShortParams
scene
的值,以商定好的統一落地頁 $LAND_PAGE
做爲 page
值,生成小程序碼。scene
參數,請求後端提供的接口,例如:/api/decodeShrotParams
而前端對於統一落地頁的邏輯處理,咱們只須要在第一個問題的基礎上,增長一個轉換短鏈參數內容的邏輯就好了:
代碼層面上,咱們咱們只須要多定義轉換短鏈參數的方式:convertScenePrams
// in /pages/land-page/index.js import { landTransferDecorator } from 'wxapp-router'; const landTransferOptions = { // 此處接收 onLoad(options) 中的 options.scene convertSceneParams: (sceneParams) => { return API.convertScene({ sceneParams }).then((content) => { // 假如後端存的是 JSON 字符串,前端decode // 要求 content = { path: '/home', a: 1, b:2 } return JSON.parse(content); }); }, }; Page({ @landTransferDecorator(landTransferOptions) onLoad(options) { // ... }, });
而其中的 API.convertScene
就對接服務端提供 HTTP 接口服務來完成。
對於小程序內部的路由跳轉,咱們除了指定一個字符串的路由,咱們是否也能夠經過鏈式調用,像調用函數那樣去跳轉頁面呢?相似這樣;
routes.pages.user.go({ name: 'jc' });
這樣作的好處是:
因爲事先 wxapp-router
並不知道開發者須要註冊的路由是什麼樣的,因此路由的 TS 聲明文件,須要開發者來定義。
例如,咱們在項目中維護一份路由文件:
// config/routes.ts // 建立路由實例 const router = new Router(); const routesConfig = [{ path: '/user', route: '/pages/user/index', }, { path: '/goods', route: '/pages/goods/index', }]; type RoutesType { paegs: { user: Route<{name: string}>, goods: Route, } } // 註冊路由 router.batchRegister(routesConfig); // 獲取 routes const routes: RoutesType = router.getRoutes(); export default routes;
而後在別的地方使用它:
import routes from './routes.ts'; routes.pages.user.go({ name: 'jc' });
若是路由變多的時候,咱們還須要對每一個路由手動去編寫 RoutesType
的話,就有點難受了。
在小程序中,咱們把正式路由都配置到 app.json
,那麼在遵循既定的項目結構狀況下,咱們能夠經過自動構建,完成大部分工做,例如:
以上都是腳本層面的使用,小程序中還有 wxml
, 咱們但願能在有個組件快速使用:
<Router path="/pageA" query="{{pageAQuery}}"></Router> <Router path="/pageB" query="{{pageBQuery}}" type="redirectTo"></Router> <Router path="/pageC/katy"></Router>
那麼,實現一個自定義組件,而後把 Router模塊包裝一下,問題就不大了。
示例代碼:
// components/router.wxml <view class="wxapp-router" bind:tap="gotoPage"> <slot /> </view>
// components/router.ts Component({ properties: { path: String, type: { type: String, value: 'gotoPage' }, route: String, query: Object, delta: Number, setData: Object, }, methods: { gotoPage(event) { const router = getApp().router; const { path, route, type, query} = this.data; const toPath = route || path; if (['gotoPage', 'navigateTo', 'switchTab', 'redirectTo'].includes(type)) { (router as any)[type](toPath, query); } if (type === 'navigateBack') { const { delta, setData } = this.data; router.navigateBack({ delta }, { setData }) } } } })
最後,咱們來總體回顧一下各模塊的設計
鑑於寫過不少的實戰類的文章,會有很多同窗想要到總體的示例代碼,此次我就索性寫了一個工具,Enjoy it!
wxapp-router: 🛵 The router for Wechat Miniprogram