在这一节,我们先回顾下小程序路由基础知识,然后针对小程序路由存在的问题做相关封装优化。
小程序路由一共有5个API:
页面间传递参数是通过 url 拼接参数的方式,如传递一个id=1的参数写法如下:
uni.navigateTo({
url: '[目标页面地址]?id=1'
})
然后在目标页面的onLoad生命周期钩子获取到上个页面传递的参数。
针对以上路由基础知识,以下的路由封装方法,主要出于以下5个目的:
通过上面的基础知识我们了解到,页面间跳转需要这么写:
uni.navigateTo({
url: '/pages/index/index'
})
这种写法无疑过于繁琐,目标希望简化成:router.navigate('index')
既然是typescript项目,我们就需要尽可能给代码完善类型提示:
1、在路由跳转时,封装的写法能够提供路由别名候选提示,并在我们写错别名时给予错误提示
router.navigate('details') // 写成detalis出现错误提示
2、获取路由参数时,路由参数应该有类型提示
原生通过参数拼接的方式,主要有以下几个弊端问题:
如上图。假设页面a传递参数给到b页面使用,而c页面也恰好需要用到a页面传递给b页面的某些参数,那么这时候就需要b页面跳去c页面时再做一次传递,这样不仅写法麻烦,也给后续的维护带来一定的麻烦。
不知道官方设计的用意如何,不支持传参不代表实际场景不需要用到。这里举个真实场景例子,证明switchTab也是有需要支持传递参数的时候:
如上图:假设我们的小程序在一进入时需要进行一个初始化设置,用户在完成某些选择之后(如选择性别、年龄等基础信息),才进入到小程序tab页面,这时候点击“点击进入”调用的是switchTab方法,我们需要将用户初始设置的参数传递到主页做进一步处理。
当然,解决的方法有多种,例如我们可以调用本地存储的方法暂存,然后再取值,但是通过路由传参统一管理参数无疑是更适合的写法
综合以上弊端问题的分析,这里的解决的方案也很简单:抛弃原生路由拼接参数传递方式,直接定义一个全局对象存储。
小程序调用navigateTo方法,实际上会创建一个新的webview,接着初始化新页面的生命周期里面的代码逻辑,如果遇到页面比较复杂的情况,初始化就可能比较耗时,导致用户点击跳转下一页会可能就会出现卡顿。此时用户可能多次点击触发navigateTo方法,进而有可能导致重复加载同一个页面。
在某些场景中,我们希望页面返回时,能够携带参数给上一级页面。例如用户在A页面点击选择地址,然后跳转去一个地址列表页面,在地址列表页面用户完成选择某项地址后,携带该地址返回,A页面接收到后再更新地址。目标希望可以这么调用:
router
.navigate('page', { id: 1 })
.then((data) => {
// 接收到上个页面返回的数据
console.log(data)
})
.catch((err) => {
console.log(err)
})
有了以上封装设计目的,接下来就开始正式上手封装代码。
我们在src目录下新建个router文件夹,里面新建index.ts、pages.ts、types.ts三个文件。
在上面的路由优化设计目标中,我们第一个希望优化的目标是希望简化路由写法成:
router.navigate('index')
这就需要我们存放一个路由页面集合,给每个页面路径设置别名,pages.ts内容增加:
// 主包
const mainPackage = {
index: '/pages/index/index',
}
// 分包
const subPackage = {
subIndex: '/package-sub/pages/index/index',
}
const pages = {
...mainPackage,
...subPackage,
}
export default pages
在index.ts中引入,并定义页面别名类型:
import pages from './pages'
type PageNames = keyof typeof pages
接下来处理路由传参,先定义一个store存放所有页面路由参数:
const routeStore: Record<PageNames, unknown> = {}
假设我们从首页点击进入课程详情页,路由需要传递一个id代表课程id,我们可以在types.ts新增:
export interface CourseDetails {
id: number
}
然后在index.ts中引入:
import { CourseDetails } from './types'
接着定义ObjectType泛型:
type ObjectType<T> = T extends 'courseDetails' ? CourseDetails : never
路由方法:
function navigate<T extends PageNames>(page: T, params: ObjectType<T>) {
routeStore[page] = params
uni.navigateTo({ url: pages[page] })
}
定义获取路由参数的方法:这里需要说明的是获取的路由参数应该是只读的,可以用vue的readonly方法包裹后返回:
import { readonly, DeepReadonly } from 'vue'
export function getRouteParams<T extends PageNames>(
page: T,
): DeepReadonly<ObjectType<T>> {
const p = routeStore[page] as ObjectType<T>
return readonly(p)
}
let navigateLock = false
function navigate<T extends PageNames>(page: T, params?: ObjectType<T>) {
if (navigateLock) return
navigateLock = true
routeStore[page] = params
uni.navigateTo({
url: pages[page],
complete() {
navigateLock = false
},
})
}
要实现这个功能,我们可以借助uniapp跨页面通信API:uni.emit及uni.emit 及uni.emit及uni.once。
(如果有不了解该的同学可以移步uniapp文档了解:点击这里)
然后在调用navigateTo进行跳转时,将事件名传递给下个页面,下个页面在调用navigateBack返回时,通过传递过来的事件名调用uni.$emit触发事件。代码逻辑如下:
let navigateLock = false
function navigate<T extends PageNames>(
page: T,
params?: ObjectType<T>,
): Promise<any> {
if (navigateLock) return
const eventName = Math.floor(Math.random() * 1000) + new Date().getTime() + '' // 生成唯一事件名
navigateLock = true
routeStore[page] = params
uni.navigateTo({
url: `${pages[page]}?eventName=${eventName}`, // 这里将触发事件名传递给下个页面
complete() {
navigateLock = false
},
})
return new Promise<any>(
(resolve, reject) => (
uni.$once(eventName, resolve), uni.$once(eventName, reject)
),
)
}
interface BackParams {
/** 返回页面层级 */
delta: number
/** 返回携带的数据 */
data: any
}
function back({ delta, data }: BackParams = { delta: 1, data: null }) {
// 获取当前路由信息
const currentRoute = getCurrentPages().pop()
// 拿到路由事件名参数
const eventName = currentRoute.options.eventName
uni.$emit(eventName, data)
uni.navigateBack({
delta,
})
}
import { CourseDetails } from './types'
import pages from './pages'
type PageNames = keyof typeof pages
type ObjectType<T> = T extends 'courseDetails' ? CourseDetails : never
const routeStore = {} as Record<PageNames, unknown>
export function getRouteParams<T extends PageNames>(
page: T,
): DeepReadonly<ObjectType<T>> {
const p = routeStore[page] as ObjectType<T>
return readonly(p)
}
let navigateLock = false
function navigate<T extends PageNames>(
page: T,
params?: ObjectType<T>,
): Promise<any> {
if (navigateLock) return
const eventName = Math.floor(Math.random() * 1000) + new Date().getTime() // 生成唯一事件名
navigateLock = true
routeStore[page] = params
uni.navigateTo({
url: `${pages[page]}?eventName=${eventName}`,
complete() {
navigateLock = false
},
})
return new Promise<any>(
(resolve, reject) => (
uni.$once(eventName, resolve), uni.$once(eventName, reject)
),
)
}
function redirect<T extends PageNames>(page: T, params?: ObjectType<T>) {
routeStore[page] = params
uni.redirectTo({ url: pages[page] })
}
function reLaunch<T extends PageNames>(page: T, params?: ObjectType<T>) {
routeStore[page] = params
uni.reLaunch({ url: pages[page] })
}
function switchTab<T extends PageNames>(page: T, params?: ObjectType<T>) {
routeStore[page] = params
uni.switchTab({ url: pages[page] })
}
interface BackParams {
/** 返回页面层级 */
delta?: number
/** 返回携带的数据 */
data?: any
}
function back({ delta, data }: BackParams = { delta: 1, data: null }) {
const currentRoute = getCurrentPages().pop()
const eventName = currentRoute.options.eventName
uni.$emit(eventName, data)
uni.navigateBack({
delta,
})
}
const router = {
navigate,
redirect,
reLaunch,
switchTab,
back,
}
export default router
function next() {
router
.navigate('courseDetails', { id: 1 })
.then((data) => {
console.log('上个页面返回的数据')
console.log(data)
})
.catch((err) => {
console.log(err)
})
}
function pageBack() {
router.back({
data: {
msg: '这是返回携带的数据',
},
})
}
以上路由传参是假设在用户从首页打开小程序的情况下,但是有些场景下,如分享进入,消息通知点击进入,这时候携带的参数只能在url中。对此,uniapp也针对获取路由参数方式做了简化,可以通过定义 props 来直接接收 url 传入的参数,而不必在onload生命周期钩子中获取。
<script setup>
// 页面可以通过定义 props 来直接接收 url 传入的参数
// 如:uni.navigateTo({ url: '/pages/index/index?id=10' })
const props = defineProps({
id: String,
});
console.log("id=" + props.id); // id=10
</script>
这时,对于一个多入口打开的页面,要采取哪种方式获取路由参数,可以根据场景值判断,详见文档场景值
至此,我们已经完成了路由的优化封装目标。现在总结下使用步骤:
type ObjectType<T> = T extends 'courseDetails'
? CourseDetails
: T extends 'order'
? Order
: never
作者:码克吐温
链接:https://juejin.cn/post/7120160996475289607
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
阅读量:2040
点赞量:0
收藏量:0