uni-simple-router 專為uniapp打造的路由器,和uniapp深度集成 通配小程式、App和H5端 H5能完全使用vue-router開發 模塊化、查詢、通配符、路由參數 使 uni-app實現嵌套路由(僅H5端完全使用vue-router) uniapp用到了很多vue的api, ...
目錄
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)
})