uni-simple-router

2022-05-30 21:00:49

uni-simple-router
  • 專為uniapp打造的路由器,和uniapp深度整合
  • 通配小程式、App和H5端
  • H5能完全使用vue-router開發
  • 模組化、查詢、萬用字元、路由引數
  • 使 uni-app實現巢狀路由(僅H5端完全使用vue-router)
  • uniapp用到了很多vue的api,但在路由管理的功能相對於vue-router還是比較欠缺的,比如全域性導航守衛

官方檔案:https://hhyang.cn/v2/start/quickstart.html

一、快速上手
// 針對uniapp HBuilder建立的專案,非cli構建
// 1⃣ NPM 安裝
npm install uni-simple-router

// 2⃣ 初始化
npm install uni-read-pages // 配合vue.config.js自動讀取pages.json作為路由表的方式,原始碼例如下:擴二

// 設定vue.config.js
const TransformPages = require('uni-read-pages')
const tfPages = new TransformPages()
module.exports = {
	configureWebpack: {
		plugins: [
			new tfPages.webpack.DefinePlugin({
        // 1⃣ 設定全域性變數
				// ROUTES: JSON.stringify(tfPages.routes) 
        // 2⃣ includes 可自定義,解析pages.json路由配欄位的設定, 預設 ['path', 'name', 'aliasPath']
        ROUTES: tfPages.webpack.DefinePlugin.runtimeValue(()=>{
          const tf = new TransformPages({
            includes:  ['path', 'name', 'aliasPath']
          })
          return JSON.stringify(tfPages.routes)
        },true)
			})
		]
	}
}

// /Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/webpack 我的webpack包路徑,在軟體contents包檔案裡軟體自帶的webpack
擴一:webpack外掛之DefinePlugin
  • 允許建立一個 在__編譯__時可以設定的__全域性變數__
  • 場景:區分不同開發模式處理
// 1⃣ 用法:每個傳進DefinePlugin的鍵值都是一個標誌或者多個用.連線起來的識別符號
new webpack.DefinePlugin({
  PRODUCTION: JSON.stringify(true),
  BROWSER_SUPPRTS_HTML5: true,
  VERSION: JSON.stringify('abcde'),
  TWO: '1+1',
  'typeof window': JSON.stringify('object')
})
// 使用方式
console.log('Running App version', VERSION)
if(!BROWSER_SUPPRTS_HTML5) require("html5shiv")

// 2⃣ 功能標記 來作為一個flag標識啟用和禁用構建中的功能
new webpack.DefinePlugin({
  'SHOW_PRESSION': JOSN.string(true)
})

// 3⃣ 服務:可以設定不同環境下的url
new webpack.DefinePlugin({
	'DEV_URL': JSON.stringify(url_dev),
  'PRO_URL': JSON.stringify(url_pro)
})
擴二:uni-read-pages 如何獲取pages.json中的路由
// 依賴的原始碼 - 通過node的path模組獲取pages.json檔案中的任何資訊 (部分是個人註釋)
const path = require('path')
const CONFIG = { includes: ['path', 'aliasPath', 'name'] } // 預設獲取路由引數屬性
// process.cwd() 返回Node.js程序的當前工作目錄
// path.resolve(url1, 'abc') => url1/abc
const rootPath = path.resolve(process.cwd(), 'node_modules'); // 獲取根路徑

/** 解析絕對路徑
 * @param {Object} dir 
 */
function resolvePath(dir) {
	return path.resolve(rootPath, dir);
}
// 類
class TransformPages {
	constructor(config) {
    // 組合 自定義獲取設定屬性
		config = { ...CONFIG, ...config }; 
		this.CONFIG = config;
    
    // ↓本機檔案路徑(HBuilderX包檔案裡自帶的webpack模組路徑) /Applications/HBuilderX.app/Contents/HBuilderX/plugins/uniapp-cli/node_modules/webpack
		this.webpack = require(resolvePath('webpack'));
		this.uniPagesJSON = require(resolvePath('@dcloudio/uni-cli-shared/lib/pages.js'));
    
    // TODO: 根據CONFIG解析pages.json中的路由資訊 和 小程式 分包下的路由
		this.routes = this.getPagesRoutes().concat(this.getNotMpRoutes());
    
	}
  // 獲取所有pages.json下的內容 返回json
	get pagesJson() {
		return this.uniPagesJSON.getPagesJson();
	}
	
  // 通過讀取pages.json檔案 生成直接可用的routes
	getPagesRoutes(pages = this.pagesJson.pages, rootPath = null) {
		const routes = [];
		for (let i = 0; i < pages.length; i++) {
			const item = pages[i];
			const route = {};
			for (let j = 0; j < this.CONFIG.includes.length; j++) {
				const key = this.CONFIG.includes[j];
				let value = item[key];
				if (key === 'path') {
					value = rootPath ? `/${rootPath}/${value}` : `/${value}`
				}
				if (key === 'aliasPath' && i == 0 && rootPath == null) {
					route[key] = route[key] || '/'
				} else if (value !== undefined) {
					route[key] = value;
				}
			}
			routes.push(route);
		}
		return routes;
	}
  
	// 解析小程式分包路徑
	getNotMpRoutes() {
		const { subPackages } = this.pagesJson;
		let routes = [];
		if (subPackages == null || subPackages.length == 0) {
			return [];
		}
		for (let i = 0; i < subPackages.length; i++) {
			const subPages = subPackages[i].pages;
			const root = subPackages[i].root;
			const subRoutes = this.getPagesRoutes(subPages, root);
			routes = routes.concat(subRoutes)
		}
		return routes
	}
	/**
	 * 單條page物件解析
	 * @param {Object} pageCallback 
	 * @param {Object} subPageCallback
	 */
	parsePages(pageCallback, subPageCallback) {
		this.uniPagesJSON.parsePages(this.pagesJson, pageCallback, subPageCallback)
	}
}
module.exports = TransformPages
二、H5模式
2.1 路由設定
import {RouterMount,createRouter} from 'uni-simple-router';
const router = createRouter({
  // 路由設定
  routes: [
    {
      path: '/pages/index/index', // 必須和pages.json中相同
      extra: {
        pageStyle: { color: '#f00' }// 及其它自定義引數
      }
    }
  ]
})
// 元件中可以通過 this.$Route 檢視路由元資訊
2.2 完全使用vue-router開發 (H5端)
  • 巢狀路由時,若使用name方式跳轉,僅支援 this.$router.push({ name:  children1 })
// 使用 vue-router開發將會失去uniapp的生命週期
const router = createRouter({
  h5: {
    vueRouterDev: true, // 完全使用vue-router開發 預設是false
  },
  // 路由設定
  routes: [
    {
      path: '/',
     	name: 'home',
      component: () => import('@/common/router/home.vue'),
      // 巢狀路由(僅H5端),小程式端會重新頁面開啟
      children: [
        {
          path: 'home/children1',
          name: 'children1',
          component: () => import('@/common/router/children1.vue')
        },
        {
          path: 'home/children2',
          name: 'children2',
          component: () => import('@/common/router/children2.vue')
        }
      ]
    }
  ]
})
2.3 H5 路由傳參
// 除vue-router外,美化url可以在基礎設定時,定義aliasPath變數,設定路由別名(瀏覽器位址列展示名稱)
// 別名的設定,如果設定了別名,通過push路徑的方式,必須使用aliasPath的路徑才能生效
const router = createRouter({
  routes:[{
        name:'router1',
        //為了相容其他端,此時的path不能少,必須和 pages.json中的頁面路徑匹配
        path: "/pages/tabbar/tabbar-1/tabbar-1",    
        aliasPath: '/tabbar-1', 
    },]
});

// uni-simple-router路由跳轉
// aliasPath命名的路由 => name傳遞引數需 params
this.$Router.push({ name: 'detail', params: { id: 1 } })
// 帶查詢引數,變成 /home/id=1  => path 對應query,params失效
this.$Router.push({ path: '/pages/index/detail', query: { id: 1 }})
2.4 H5端路由捕獲所有路由或404路由
path:'*' // 通常用於使用者端404錯誤,如果是history模式,需要正確設定伺服器
path: '/detail-*'
// 路由的優先順序根據 定義的先後
2.5 路由懶載入
  • 打包構建應用時,Javascript包會變得非常大,影響頁面載入,把不同路由對應的元件分割成不同的程式碼塊,然後當路由被存取的時候才載入對應元件。

  •   const Foo = () => import('./Foo.vue')
      // 把元件按組分塊 使用 命名 chunk
      const Foo = () => import(/*webpackChunkName:"group-foo"*/ './Foo.vue')
      const Bar = () => import(/*webpackChunkName:"group-foo"*/ './Bar.vue')
    
三、小程式模式
  • 注:小程式系列無法攔截原生tabbar及原生導航返回,如需攔截使用自定義tabbar、header

  • 通過api進行切換時,像uni.switchTab()和this.$Router.pushTab()方法會觸發攔截的;僅底部原生tabbar進行切換時不觸發攔截

  • 強制觸發守衛:forceGuardEach(replaceAll, false) 每次呼叫api都會重新按流程觸發已經宣告的所有守衛

    • 小程式端預設:外掛api跳轉、uni導航api跳轉和首屏載入
    • 使用路由守衛:通過點選事件強制觸發、混入到onshow回撥觸發
  • 跳轉路由鎖:animationDuration保留給redirection\push足夠時間,等切換完成頁面後才放行下次跳轉

    const router = createRouter({
      platform: process.env.VUE_APP_PLATFORM,
      // ① 路由鎖
      applet: {
        animationDuration: 300 // 預設300ms
        // animationDuration: 0 // 不精準 只捕捉跳轉api下的complete函數
      },
      // ②優雅解鎖 error.type: 0 表示 next(false)、1表示next(unknownType)、2表示加鎖狀態,禁止跳轉、3表示在獲取頁面棧時,頁面棧不夠level獲取
      routerErrorEach:(error, router) => {
        if (error.type === 3) {
          router.$lockStatus = false
        }
      },
      routes: [...ROUTES]
    })
    
四、路由跳轉
4.1 元件跳轉
  • vue-router中可以通過router-link元件進行頁面跳轉,uni-simple-router也提供了類似的元件,需要手動註冊

  •   // main.js
      import Link from './node_modules/uni-simple-router/dist/link.vue'
      Vue.component('Link', Link)
    
  •   // 通過path直接跳轉 並指定跳轉型別
      <Link to="/tabbar1" navType="pushTab">
      	<button type="primary">使用path物件跳轉</button>
      </Link>
    
4.2 程式設計式導航
  • 通過this.$Router獲取路由物件;push、pushTab、replace、back等api進行路由跳轉
  • 注:path搭配query引數、name搭配params引數
  • 導航使用方式同vue-router
五、跨平臺模式
5.1 提前享用生命週期
  • uniapp由於只用onLoad接受options引數、onShow不接受;傳遞深度物件引數時,需要先編碼再傳遞解碼

  •   // 動態改變引數 使 onLoad和onShow支援options
      const router = createRouter({
      	platform: process.env.VUE_APP_PLATFORM,  
      	routes: [...ROUTES],
      	beforeProxyHooks: {
      		onLoad(options, next){
      			next([router.currentRoute.query]);
      		},
      		onShow([options], next){
      			console.log(this);
      			const args=options||router.currentRoute.query;
      			next([args]);
      		},
      	},
      });
    
5.2 導航守衛
  • 全域性前置守衛

    /**
    * to: Route 即將進入的目標
    * from: Route 當前導航正要離開的路由
    * next: Function 該方法的resolve勾點函數必須呼叫,執行效果依賴next方法的呼叫引數
    * -- next()呼叫引數:管道中的下個勾點; next(false)中斷當前導航;
    * -- next('/')/({path: '/'})跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航
    * -- next({delta: 2, NAVTYPE: 'back'}) 中斷當前導航,呼叫新的跳轉型別,同時返回兩層頁面
    **/
    router.beforeEach((to, from, next) => {
      // ...
      // 1⃣ next()
      // 2⃣ NAVTYPE定義跳轉方式,兩者相同非必填
      if (to.name == 'tabbar-5') {
            next({
                name: 'router4',
                params: {
                    msg: '我攔截了tab5並重定向到了路由4頁面上',
                },
                NAVTYPE: 'push'
            });
        } else{
        next();
      }
    })
    
  • 全域性後置守衛

    // 不接受next函數也不改變導航本身
    router.afterEach((to,from) => {})
    
  • 路由獨享守衛

    // 路由設定上直接定義beforeEnter守衛
    const router = createRouter({
      routes: [{
        path: '/pages/home/index',
        beforeEnter:(to,from,next) => {
         // 引數同上全域性前置守衛 
          next()
        }
      }]
    })
    
  • 元件內的守衛

    // 元件內設定beforeRouteLeave守衛 - 直接呼叫beforeRouteLeave方法
    export default {
      beforeRouteLeave(to, from, next) {
        // 導航離開該元件的對應路由時呼叫
        // 可以存取元件範例this
        next()
      }
    }
    
六、路由守衛-模組化
// 1⃣ 建立 router資料夾,模組化設定 檔案結構
|+------------------------+|
| router                   |
| |+--------------------+| |
| | modules              | |
| | |+----------------+| | |
| | | home.js          | | |
| | | index.js         | | |
| | |+----------------+| | |
| |+--------------------+| |
| index.js                 |
|+------------------------+|
// home.js
const home = [
  {
    path: '/pages/home/index',
    name: 'home'
  }
]
export default home
// modules下的index.js是一個模組讀取
// ① require.context(directory, useSubdirectories, regExp) 具體詳情:如下擴三
const files = require.contxt('.',false,/.js$/)
const modules = []

files.keys().froEach(key => {
  if (key === './index.js') return
  const item = files(key).default
  modules.push(...item)
})

export default modules // 將所有模組的路由模組整合到一起, routes Array
// router下的index.js 路由守衛
import modules from './modules/index.js'
import Vue from 'vue'
import CreateRouter from 'uni-simple-router'
import store from '@/store/store.js'

Vue.use(CreateRouter)
//初始化
const router = new CreateRouter({
 APP: {
  holdTabbar: false //預設true
 },
 h5: {
  vueRouterDev: true, //完全使用vue-router開發 預設 false  
 },
 // 也可以 通過uni-read-pages來讀取pages.json檔案的路由表,配合vue.config.js
 // router: [...ROUTES] // ROUTES是通過webpack的defaultPlugin編譯成全域性變數,具體操作上文
 routes: [...modules] //路由表
});

//全域性路由前置守衛
router.beforeEach((to, from, next) => {
 // 首先判斷是否存在路由資訊
 //不存在就先呼叫介面得到資料
})
// 全域性路由後置守衛
router.afterEach((to, from) => {})
export default router;
擴三、require.context用法
// require.context(directory, useSubdirectories, regExp)
// directory: 表示檢索的目錄
// useSubdirectories: 表示是否檢索子資料夾
// regExp: 匹配檔案的正規表示式
// 返回值: resolve是一個函數,返回已解析請求的模組ID; keys是一個函數,它返回上下文模組可以處理的所有可能請求的陣列;
// 使用場景:①用來元件內引入多個元件;②在main.js內引入大量公共元件;
// ①元件內引入多個元件 - webpack
const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/) //值型別 ['./home.js', 'detail.js',...]
const modules = {}

files.keys().forEach(key => {
  const name = paths.basename(key, '.vue') // 去掉檔名的 .vue字尾
  modules[name] = files(key).default || files(key)
})
// modules { home: '{module樣式路徑}', detail: '{}', ...  }
export default { 
  	...,
  	data() { return {}}, 
    components: modules
}
// ②在main.js內引入大量公共元件
import Vue from 'vue'
// 引入自定義元件
const requireComponents = require.context('../views/components',true,'/\.vue/')
// 遍歷出每個陣列的路徑
requireComponents.keys().forEach(fileName => {
  const reqCom = requireComponents(fileName)
  // 獲取元件名
  const reqComName = reqCom.name || fileName.replace(/\.\/(.*)\.vue/, '$1')
  // 元件掛載
  Vue.components(reqComName, reqCom.default || reqCom)
})