路由跳转是小程序开发中常见的功能,这篇文章将带着大家详细解读小程序路由相关的API,探究小程序+H5复合路由跳转的常见问题和解法。
小程序跳转有如下API:
保留当前页面,并跳转到应用内的某个页面,但不能跳到 tabbar
页面。使用 wx.navigateBack
可以返回到原页面,在小程序中页面的栈最多保留十层。
关闭当前页面,返回上一或多级页面,可以通过 getCurrentPages
获取当前页面的栈,以决定需要返回几层。
关闭当前页面,打开并跳转到应用内的某个页面,但不允许跳转到 tabbar
页面。
关闭所有页面,打开并跳转到应用内的某个页面。
跳转到 tabBar
页面并关闭其他所有非 tabBar
的页面。
在小程序中我们开发的新功能都是需要提交到微信后台进行审核的,对于要频繁迭代的业务,我们希望实现免审核即时发版,并能复用原H5的页面流程,这里就需要用到小程序提供的 web-view 组件了。
<web-view :src="src"></web-view>
web-view 是承载网页的容器,其中 src
是 webview
指向网页的链接。
web-view 网页中可以使用 JSSDK
提供的接口返回小程序页面。
以 vue 项目为例,我们只需在 public
目录下的 index.html
文件中引入 JSSDK
即可使用到微信小程序提供的接口能力。
<!-- index.html -->
<script src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
通过 wx.login
接口和我们的后端接口交互,获取 token 并存储, 在加载 web-view 页面时直接传递 token 参数。
<!-- 小程序 webview 页面 -->
<web-view :src="`${url}?token=${token}`"></web-view>
之后在我们自己的站点上获取 URL 中 query 里的 token 参数即可实现鉴权。
<!-- 自己站点的业务页面 -->
<script>
import Cookies from "js-cookie"
export default {
mounted() {
const token = this.$route.query.token
Cookies.set("token", token, {
expires: 30,
domain: ".xxx.com"
})
}
}
</script>
当然,为了安全考虑,还应对 token 进行加解密操作。
<!-- webview 页面代码示例 -->
<template>
<web-view :src="url"></web-view>
</template>
<script>
// encrypt 函数自行实现
import { encrypt } from 'xxx'
export default {
data() {
return {
url: '',
}
},
onLoad(query) {
const { url } = query
const token = getApp().globalData.token
if (url) {
const encryptToken = encrypt(token)
const urlWithToken = `${url}?token=${encryptToken}`
this.url = urlWithToken
}
}
}
</script>
答案是可以的,这是一个非常方便的特性。如上文所述,在我们小程序的 webview 页面中,会涉及到 token 的获取、加密 和 URL 拼接等操作。redirectTo / navigateTo 可使用同一个 webview 容器打开其他 H5 页面,这样就避免封装多个 webview 容器实现类似的逻辑。
<!-- 自己站点的业务页面A -->
<script>
export default {
methods: {
redirect(url) {
// 跳转时复用同一个 webview 页面
const nextUrl = encodeURIComponent(url)
wx.miniProgram.navigateTo({
url: `/pages/webview?url=${nextUrl}`
})
}
},
};
</script>
postMessage
是 web-view 组件中的 H5 页面 与 小程序通信的主要方式。
小程序在特定时机,比如后退、组件销毁、分享等,触发组件的 message 事件。我们可以利用 message 事件去执行埋点之类的逻辑。这里要注意的是在 web-view 中触发 postMessage
的时机并不是很符合我们的直觉。
如上图,当我们进入 webview 容器后在 webview 中的 A 页面通过 location.href
跳转到 B 页面时,点击小程序左上角返回是不会触发 postMessage 事件的,也就是说"小程序后退"指的是关闭页面容器,而不是执行后退事件。
再如上图,当 webview 容器栈中只剩 A 页面时,点击左上角返回则会关闭当前页面容器,在这时才会触发 postMessage 事件。
有时候,我们从B页面回到A页面,点击左上角返回的时候不希望回到B页面,而是希望跳转到其他页面,这个时候就需要 hook 浏览器的返回事件。
原生小程序页面可以通过 onShow
hook函数监听返回事件,但在 web-view 容器中的 H5 页面就只能通过一些 hack 的手段来实现,如下代码所示:
// 先给浏览器压一个栈,以便拦截浏览器的返回事件
history.pushState({}, '');
// 监听返回事件, 自定义后退行为
window.addEventListener('popstate', () => {
// 做一些事情
});
在 iOS 中,该方法是可以完美工作的。但遗憾的是,在 Android 中,用户必须和页面有所交互,比如滑动一下页面后才会触发 popstate
,之后才能监听到返回事件。
如图,在某些业务场景下,我们从页面A跳转到页面B填写了一些表单,当从页面B navigateBack
回来的时候,希望刷新页面A的数据,这个时候我们该如何实现?
在 Hybrid App 里会通过js-bridge 提供 resumeListener
等API来让我们监听页面状态。但在小程序的 JSSDK 中并没有提供类似的方法,我们也无法使用原生小程序的 onShow
钩子函数去刷新H5页面,于是我们似乎陷入了一个困境 :(。
一开始,我们想到一个比较符合直觉的解法:
<!-- 自己站点的 H5 业务页面B -->
<script>
export default {
methods: {
goBack() {
wx.miniProgram.postMessage({
data: {
action: 'refresh'
}
})
wx.miniProgram.navigateBack()
}
},
}
</script>
<!-- webview页面代码逻辑 -->
<template>
<web-view :src="url" @message="handleMessage"></web-view>
</template>
<script>
export default {
data() {
return {
url: '',
}
},
onLoad(query) {
// 省略...
},
methods: {
handleMessage(e) {
const length = e.detail.data.length
const msg = e.detail.data[length - 1]
const { action } = msg
if (action === 'refresh') {
const tempUrl = this.url
this.url = ''
setTimeout(() => {
this.url = tempUrl
}, 500)
}
}
}
}
</script>
在业务页面B navigateBack
回业务页面A的时候,postMessage
发送一个消息通知给 webviewA 以刷新页面,然后 webviewA 中接收到 message
消息把当前 url
置空再重置。看起来很完美,只可惜,这种方式并没有效果 :(。
在绝望之际,幸运的是,我们又想到了使用 Cookie 来传递页面刷新的信号:
<!-- 自己站点的 H5 业务页面B -->
<script>
import Cookies from "js-cookie"
export default {
methods: {
goBack() {
Cookies.set('refresh', 'true', {
domain: ".xxx.com"
})
wx.miniProgram.navigateBack()
}
},
}
</script>
<!-- 自己站点的 H5 业务页面A -->
<script>
import Cookies from "js-cookie"
export default {
mounted() {
this.listenCookieChange()
}
methods: {
listenCookieChange() {
var refresh = Cookies.get('refresh')
if (refresh === 'true') {
Cookies.remove('refresh')
console.log('刷新页面')
// 刷新页面的逻辑
} else {
console.log('继续轮询')
setTimeout(this.listenCookieChange, 1000)
}
}
},
}
</script>
在业务页面A的 mounted
方法中轮询 Cookie 的 refresh
值的变化,在业务页面B 执行navigateBack
之前在 Cookie 里面设置 refresh
属性为 true
,这样业务页面A接受到 refresh
信号时即可刷新页面,且刷新页面后立刻删除了 Cookie 中的 refresh
信号 ,这样也不会带来额外的维护成本。
注:现在已经有了 CookieChangeEvent
这个API可以优化掉这种轮询的方式,但是该API还在实验阶段,目前并不适合在生产使用。
以上就是小程序和H5复合路由的揭秘,主要分享了小程序原生路由相关的API,H5页面与原生小程序之间的通信,以及小程序路由跳转的常见问题与解法,希望大家有所收获。
EverCoder,借款前端开发,负责主营小程序和拍多赚的开发,喜欢整洁的代码,画靓靓的页面
CC,负责借款前端团队,负责大前端技术委员会
招聘信息
Java、大数据、前端、测试等各种技术岗位热招中,欢迎扫码了解~