目录
- uni-simple-router
- 一、快速上手
- 扩一:webpack插件之DefinePlugin
- 扩二:uni-read-pages 如何获取pages.json中的路由
- 二、H5模式
- 2.1 路由配置
- 2.2 完全使用vue-router开发 (H5端)
- 2.3 H5 路由传参
- 2.4 H5端路由捕获所有路由或404路由
- 2.5 路由懒加载
- 三、小程序模式
- 四、路由跳转
- 4.1 组件跳转
- 4.2 编程式导航
- 五、跨平台模式
- 5.1 提前享用生命周期
- 5.2 导航守卫
- 六、路由守卫-模块化
- 扩三、require.context用法
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.context('.',false,/.js$/) | |
const modules = [] | |
files.keys().forEach(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) | |
}) |