乐于分享
好东西不私藏

Uniapp路由相关功能及其应用深度解剖教程

Uniapp路由相关功能及其应用深度解剖教程

书接上篇,我们今天继续来学习一下 UniApp 聊一个项目中必用的技术:UniApp路由进阶,路由模式、传参与拦截器跨平台实战,附带实现思路和所有源码示例,以及常见避坑指南和开发实践。

此系列文章将带领你从移动端跨平台开发入门到精通,如果你也喜欢关注APP、小程序、公众号、H5等等应用的开发,可以持续关注后续更新,避免错过宝贵的知识分享。

致开发者的忠告: AI编程盛行的今天,我们并不是不需要学习技术,而是更应该专研技术,拥有把控全局的架构设计思维才能在AI盛行的未来有立足之地。

言归正传,之前我们聊过UniApp的基础路由API,今天我们来深入探讨一些更进阶的话题:路由模式(hash/history)、各种传参方式的坑、以及路由拦截器的实现。这些知识在开发复杂应用时至关重要,尤其是在跨平台场景下,一不小心就会踩坑。

很多同学会遇到这些问题:

  • 在H5用得好好的路由,部署到服务器刷新就404。

  • 传参里带了特殊字符,接收时被截断。

  • 想在微信小程序实现登录拦截,结果switchTab绕过拦截。

  • 微信公众号里跳转小程序,死活打不开。

  • 从Vue转UniApp,总是不自觉地想用this.$router.push

别担心,今天我就带你系统地梳理一遍,把这些坑都填平。


一、路由模式:H5端特有的选择

在开始之前,我们要明确一点:路由模式(hash/history)只影响H5端。小程序和App端使用的是原生导航机制,不受这个配置影响。

1.1 Hash 模式 vs History 模式

对比维度 Hash 模式 History 模式
URL 示例 http://example.com/#/pages/index/index http://example.com/pages/index/index
原理 利用URL中#后的部分模拟路由,改变#不触发页面刷新 利用HTML5 History API,修改浏览器历史记录
服务器配置 无需特殊配置 需要配置所有路由指向index.html
SEO 搜索引擎可能忽略#后内容 URL规范,对SEO友好
兼容性 极佳(支持所有浏览器) 需要支持History API的现代浏览器
常见问题 URL带#不美观 刷新或直接访问子路由返回404

1.2 如何在UniApp中配置路由模式

manifest.json的H5配置中设置:

{"h5": {"router": {"mode""history"// 可选 "hash" 或 "history"    }  }}

1.3 History模式的后端配置(重要!)

如果你选择history模式,必须在服务器端配置重定向规则,否则用户直接访问子路由或刷新页面会返回404。

Nginx配置示例

location / {try_files$uri $uri/ /index.html;}

如果部署在子目录(比如/app/下):

location^~ /app/ {try_files$uri $uri/ /app/index.html;}

Apache配置示例

<IfModule mod_rewrite.c>  RewriteEngine On  RewriteBase /  RewriteRule ^index\.html$ - [L]  RewriteCond %{REQUEST_FILENAME} !-f  RewriteCond %{REQUEST_FILENAME} !-d  RewriteRule . /index.html [L]</IfModule>

1.4 如何选择?

  • 选Hash模式:追求部署简便、无需服务器配置,或需兼容老旧环境。

  • 选History模式:需要美观的URL、优化SEO,且能配置服务器支持。


二、路由传参的N种姿势与避坑指南

2.1 URL传参(最常用)

// 发送页uni.navigateTo({url'/pages/detail/detail?id=123&name='+encodeURIComponent('张三&李四')})// 接收页(onLoad中接收)onLoad(options) {console.log(options.id)      // '123'console.log(options.name)    // '张三&李四'(已自动解码)}

注意点

  • 参数值会被自动decodeURIComponent,所以你不需要手动解码。

  • 但发送前如果参数包含特殊字符(&、=、?、#),必须用encodeURIComponent编码

  • 参数值默认都是字符串,数字需自行转换parseInt

  • URL长度有限制(小程序约2KB),不适合传大量数据。

2.2 通过query传参(另一种写法)

uni.navigateTo({url'/pages/detail/detail',query: {id123,name'张三'  },success: (res=> {console.log('跳转成功')  }})

接收方式同样是在onLoad中通过options获取。

2.3 事件总线传参(适合跨页面、返回传参)

// 页面A - 发送uni.$emit('updateUser', { name'张三' })uni.navigateBack()// 页面B - 接收(在onLoad中监听)onLoad() {uni.$on('updateUser', (data=> {console.log(data// { name: '张三' }  })}onUnload() {uni.$off('updateUser'// 必须移除监听,否则内存泄漏!}

适用场景

  • 从详情页返回列表页,刷新列表。

  • 兄弟组件通信。

2.4 全局存储传参(Vuex/Pinia/Storage)

适合传递需要长期保存或跨多页的数据,比如用户信息、购物车数据。

// 发送页uni.setStorageSync('orderInfo', { id123 })// 接收页onShow() {constorderInfo=uni.getStorageSync('orderInfo')// 使用后建议移除,避免污染uni.removeStorageSync('orderInfo')}

2.5 EventChannel传参(页面间双向通信)

这是UniApp提供的一种更高级的传参方式,适合需要从目标页返回数据给源页的场景。

// 源页面uni.navigateTo({url'/pages/detail/detail',events: {// 监听目标页返回的数据acceptDataFromDetailfunction(data) {console.log('从详情页返回的数据:'data)    }  },success(res) {// 通过eventChannel向目标页传递数据res.eventChannel.emit('acceptDataFromOpener', { from'列表页' })  }})// 目标页面(detail.vue)onLoad(options) {consteventChannel=this.getOpenerEventChannel()// 向源页发送数据eventChannel.emit('acceptDataFromDetail', { result'操作成功' })// 监听源页传来的数据eventChannel.on('acceptDataFromOpener', (data=> {console.log('收到源页数据:'data)  })}

三、跨平台路由陷阱与解决方案

3.1 微信小程序:页面栈限制

问题:小程序页面栈最多10层,超过后navigateTo会失败。

解决方案:监听页面栈深度,超过阈值改用redirectToreLaunch

constpages=getCurrentPages()if (pages.length>=8) { // 留点余量uni.redirectTo({ url })else {uni.navigateTo({ url })}

3.2 微信小程序:switchTab不能传参

问题uni.switchTab不支持在URL后带参数。

解决方案:用全局变量或storage。

// 发送页uni.setStorageSync('tabParams', { id123 })uni.switchTab({ url'/pages/index/index' })// 接收页(在onShow中读取)onShow() {constparams=uni.getStorageSync('tabParams')if (params) {// 使用后移除uni.removeStorageSync('tabParams')  }}

3.3 微信公众号:History模式导致JS-SDK签名失败

问题:在iOS下,微信会缓存第一次进入的页面地址。如果使用history模式,从一级页面跳到二级页面,路由地址变了,但微信缓存的地址没变,导致wx.config签名失败,进而导致wx-open-launch-weapp等标签无法使用。

原因:iOS有缓存机制,Android没有。跳转后,微信访问的当前页面url和调wx.config签名用到的url对不上。

解决方案

  1. 在需要使用JS-SDK的每个页面都重新调用wx.config

  2. 或者改用hash模式(推荐)。

3.4 微信公众号:跳转小程序路径配置

问题:公众号自定义菜单或图文消息中配置跳转小程序,路径填写错误导致无法跳转。

解决方案

  • 路径区分大小写,必须与小程序代码中的路径完全一致。

  • 如果需要带参数,格式如pages/detail/detail?id=123

  • 确保小程序已关联到公众号。

3.5 App端:plus扩展与外部跳转

App端可以使用plus.runtime打开外部应用或网页:

// 打开外部浏览器plus.runtime.openURL('https://www.baidu.com')// 打开其他App(需知道包名)plus.runtime.launchApplication({pname'com.tencent.mm'// 微信包名})

3.6 与Vue Router的差异(让熟悉Vue的同学快速上手)

对比项 Vue Router UniApp 路由 避坑指南
路由声明 路由表配置 pages.json声明页面 不要试图用Vue Router替代原生路由
跳转API router.push uni.navigateTo 小程序端不能用this.$router.push
接收参数 $route.query onLoadoptions 参数不会自动转数字
路由守卫 beforeEach uni.addInterceptor 需手动拦截所有跳转API
动态路由 支持/user/:id 不支持,只能通过URL参数 onLoadoptions获取
嵌套路由 支持 不支持 用组件嵌套模拟

重要提醒:虽然UniApp支持this.$router.push(会映射到原生跳转API),但不推荐混用,容易造成混乱。


四、路由拦截器:实现权限控制

UniApp没有内置类似Vue Router的路由守卫,但我们可以用uni.addInterceptor来实现。

4.1 基础实现

// utils/route-guard.jsconstrouteConfig= {// 白名单:无需登录即可访问的页面whiteListnewSet(['/pages/login/login','/pages/register/register','/pages/index/index'  ]),loginPage'/pages/login/login',tokenKey'token'}constcheckRoutePermission= (url=> {constpath=url.split('?')[0]// 放行白名单if (routeConfig.whiteList.has(path)) {returntrue  }// 检查登录状态consttoken=uni.getStorageSync(routeConfig.tokenKey)return!!token}constrouteInterceptor= {invoke(args) {console.log('路由拦截:'args.url)if (checkRoutePermission(args.url)) {returntrue// 放行    }// 无权限:跳转到登录页,并携带redirect参数constredirectUrl=encodeURIComponent(args.url)uni.redirectTo({url`${routeConfig.loginPage}?redirect=${redirectUrl}`    })returnfalse// 阻止原跳转  }}exportconstinitRouteGuard= () => {constmethods= ['navigateTo''redirectTo''reLaunch''switchTab']methods.forEach(method=> {uni.addInterceptor(methodrouteInterceptor)  })}

4.2 在App.vue中初始化

<script>import { initRouteGuard } from'@/utils/route-guard'exportdefault {onLaunch() {initRouteGuard()  }}</script>

4.3 登录页处理(带重定向)

<scriptsetup>import { onLoad } from'@dcloudio/uni-app'constredirect=ref('')onLoad((options=> {if (options.redirect) {redirect.value=decodeURIComponent(options.redirect)  }})constlogin= () => {// 模拟登录成功uni.setStorageSync('token''fake-token')if (redirect.value) {// 判断原页面类型if (redirect.value.includes('/pages/user/user')) {uni.switchTab({ urlredirect.value })    } else {uni.redirectTo({ urlredirect.value })    }  } else {uni.switchTab({ url'/pages/index/index' })  }}</script>

4.4 进阶:不同用户角色的权限控制

constcheckRoutePermission= (url=> {constpath=url.split('?')[0]if (routeConfig.whiteList.has(path)) returntrueconsttoken=uni.getStorageSync('token')if (!tokenreturnfalse// 根据角色检查constrole=uni.getStorageSync('userRole'||'user'// 管理员专属页面if (path.startsWith('/pages/admin/'&&role!=='admin') {returnfalse  }// VIP专属页面if (path.startsWith('/pages/vip/'&&role!=='vip') {returnfalse  }returntrue}

五、完整代码示例:带权限控制的演示项目

项目结构

pages/  index/index.vue          # 首页(公开)  detail/detail.vue        # 详情页(需登录)  user/user.vue            # 个人中心(需登录,tab页)  login/login.vue          # 登录页(公开)utils/  route-guard.js           # 路由拦截器App.vue                    # 应用入口pages.json                 # 页面配置

5.1 pages.json配置

{"pages": [    {"path""pages/index/index""style": {"navigationBarTitleText""首页"}},    {"path""pages/detail/detail""style": {"navigationBarTitleText""详情"}},    {"path""pages/user/user""style": {"navigationBarTitleText""我的"}},    {"path""pages/login/login""style": {"navigationBarTitleText""登录"}}  ],"tabBar": {"list": [      {"pagePath""pages/index/index""text""首页"},      {"pagePath""pages/user/user""text""我的"}    ]  }}

5.2 完整路由拦截器(utils/route-guard.js)

/** * 全局路由守卫 * 功能:实现页面跳转的权限校验 * 使用:在App.vue的onLaunch中调用initRouteGuard() */constrouteConfig= {whiteListnewSet(['/pages/index/index','/pages/login/login','/pages/register/register'  ]),loginPage'/pages/login/login',tokenKey'token'}constcheckRoutePermission= (url=> {constpath=url.split('?')[0]if (routeConfig.whiteList.has(path)) {returntrue  }consttoken=uni.getStorageSync(routeConfig.tokenKey)if (token&&typeoftoken==='string'&&token.length>0) {returntrue  }returnfalse}constrouteInterceptor= {invoke(args) {console.log('🚦 路由拦截:'args.url)if (checkRoutePermission(args.url)) {returntrue    }console.warn('❌ 无权限,跳转登录页')constredirectUrl=encodeURIComponent(args.url)// 判断原跳转方法,保留跳转类型if (args.type==='switchTab') {uni.switchTab({url`${routeConfig.loginPage}?redirect=${redirectUrl}`      })    } else {uni.redirectTo({url`${routeConfig.loginPage}?redirect=${redirectUrl}`      })    }returnfalse  }}exportconstinitRouteGuard= () => {try {constmethods= ['navigateTo''redirectTo''reLaunch''switchTab']methods.forEach(method=> {uni.addInterceptor(methodrouteInterceptor)    })console.log('✅ 路由守卫已启用')  } catch (error) {console.error('❌ 路由守卫初始化失败:'error)  }}exportconstupdateWhiteList= (newRoutes=> {routeConfig.whiteList=newSet([...routeConfig.whiteList...newRoutes])}

5.3 首页(pages/index/index.vue)

<template><viewclass="p-20"><button@click="goToDetail">去详情页(需登录)</button><button@click="goToUser">去个人中心(需登录)</button><button@click="goToLogin">去登录页</button><button@click="goToDetailWithParams">传参示例</button></view></template><scriptsetup>constgoToDetail= () => {uni.navigateTo({ url'/pages/detail/detail' })}constgoToUser= () => {uni.switchTab({ url'/pages/user/user' })}constgoToLogin= () => {uni.navigateTo({ url'/pages/login/login' })}constgoToDetailWithParams= () => {constname=encodeURIComponent('张三&李四')uni.navigateTo({ url`/pages/detail/detail?id=123&name=${name}&from=index`  })}</script>

5.4 详情页(pages/detail/detail.vue)

<template><viewclass="p-20"><view>商品ID:{{ id }}</view><view>名称:{{ name }}</view><view>来源:{{ from }}</view><button@click="addToCart">加入购物车并返回</button></view></template><scriptsetup>import { ref } from'vue'import { onLoad } from'@dcloudio/uni-app'constid=ref('')constname=ref('')constfrom=ref('')onLoad((options=> {console.log('接收参数:'options)id.value=options.idname.value=options.name||''from.value=options.from||''})constaddToCart= () => {// 模拟加入购物车uni.showToast({ title'已加入购物车'icon'success' })// 返回上一页并传递数据(事件总线示例)uni.$emit('cart-updated', { count1 })uni.navigateBack()}</script>

5.5 个人中心(pages/user/user.vue)- tabBar页

<template><viewclass="p-20"><textv-if="user">欢迎 {{ user.name }}</text><textv-else>未登录(理论上不会显示,因为拦截器会拦截)</text><button@click="logout"v-if="user">退出登录</button></view></template><scriptsetup>import { ref } from'vue'import { onShow } from'@dcloudio/uni-app'constuser=ref(null)onShow(() => {consttoken=uni.getStorageSync('token')if (token) {user.value= { name'张三' }  } else {user.value=null  }})constlogout= () => {uni.removeStorageSync('token')uni.showToast({ title'已退出'icon'success' })user.value=null}</script>

5.6 登录页(pages/login/login.vue)

<template><viewclass="p-20"><button@click="login">模拟登录</button></view></template><scriptsetup>import { ref } from'vue'import { onLoad } from'@dcloudio/uni-app'constredirect=ref('')onLoad((options=> {if (options.redirect) {redirect.value=decodeURIComponent(options.redirect)  }})constlogin= () => {uni.setStorageSync('token''fake-token')uni.showToast({ title'登录成功'icon'success' })if (redirect.value) {// 判断原页面是否为tabBar页(简单判断)if (redirect.value.includes('/pages/user/user')) {uni.switchTab({ urlredirect.value })    } else {uni.redirectTo({ urlredirect.value })    }  } else {uni.switchTab({ url'/pages/index/index' })  }}</script>

5.7 App.vue – 初始化

<script>import { initRouteGuard } from'@/utils/route-guard'exportdefault {onLaunch() {initRouteGuard()  }}</script>

六、常见错误与解决方案

❌ 错误1:H5端history模式部署后刷新404

原因:服务器未配置重定向规则。解决方案:按前文所述配置Nginx/Apache。

❌ 错误2:URL传参,参数被截断

原因:参数中包含了&=等特殊字符。解决方案:用encodeURIComponent编码。

❌ 错误3:小程序页面栈溢出

原因:连续navigateTo超过10次。解决方案:监听页面栈深度,适时改用redirectTo

❌ 错误4:switchTab传参无效

原因:官方不支持。解决方案:用全局存储。

❌ 错误5:微信公众号跳转小程序失败

原因:JS-SDK签名失败,或路径配置错误。解决方案:检查签名逻辑,确保路径大小写正确,考虑用hash模式。

❌ 错误6:拦截器没有拦截switchTab

原因:只拦截了navigateTo解决方案:拦截所有四个跳转API。

❌ 错误7:事件总线监听导致内存泄漏

原因:监听了但没有在onUnload中移除。解决方案:成对使用uni.$onuni.$off


七、总结

今天我们深入学习了UniApp路由的方方面面:

  • 路由模式:hash简单,history美观但需后端支持。

  • 传参方式:URL传参最简单,注意编码;EventChannel适合双向通信;事件总线适合跨页面。

  • 跨平台坑点:小程序页面栈、公众号history兼容性、tabBar传参等。

  • 路由拦截:用addInterceptor实现全局权限控制。

最后送大家一句话:路由是应用的骨架,理解了路由,就理解了应用的脉络。在跨平台开发中,多测试、多总结,才能写出健壮的代码。

如果在实际项目中遇到其他奇怪的问题,欢迎带着代码来问我。

—— 一个在路由坑里摸爬滚打多年的老前辈 !

加油,未来的全栈大佬!💪如果你也对移动端跨端开发感兴趣,关注我,后续还有更多优质文章分享!

往期相关文章推荐

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Uniapp路由相关功能及其应用深度解剖教程

评论 抢沙发

9 + 8 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮