本文将使用vue3
与ts
从零实现一个类vueuse
的useRouteQuery
方法,接受基本相同的参数(移除了router与route参数),并解决vueuse
的useRouteQuery
方法存在的一些问题。
使用 vueuse 的 useRouteQuery 碰到的问题
问:为什么不使用vueuse
的提供的useRouteQuery
方法?
答:因为在使用<KeepAlive>
保活的页面级组件之间切换时,在所有组件中使用vueuse
的useRouteQuery
方法定义的变量都会更新。
例如:打开A页面(保活)后,修改page为2,假设url现在为/pageA?page=2
,然后切换到B页面,A页面将会触发watch
且page
将会更新为默认值,并且每次修改B页面的query都会触发A页面的query更新。代码如下。
import { useRouteQuery } from '@vueuse/router'
import { watch } from 'vue'
// 页面A (KeepAlive)
const page = useRouteQuery('page', 1, { transform: Number })
watch(page, (value) => {
console.log('page A', value)
})
// 页面B (KeepAlive)
const pageSize = useRouteQuery('pagesize', 10, { transform: Number })
watch(pageSize, (value) => {
console.log('page B', value)
})
查看vueuse
的useRouteQuery
的源码后发现它是根据router
对象去保存query
信息的,因此每次url
的query
变化时,所有已存在的相关变量都会更新。
从零实现一个 useRouteQuery
思考:
- 监听
route.query
的变化,在值变化时更新响应式变量的值; - 监听响应式变量的变化,在值变化时修改
route.query
的值; - 在
route.query
变化时,判断是否是当前页面,不是则跳过更新过程。
1. 简易实现
import { watch, ref, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
type IQuery = string | number | string[] | null | undefined
/**
* 获取当前页面的query
* @param name
* @param defaultValue
* @param options
* @returns
*/
export const useRouteQuery = <T extends IQuery, K extends IQuery = T>(
name: string,
defaultValue?: T,
options: {
transform?: (value: any) => K
mode?: 'push' | 'replace'
isEncodeURIComponent?: boolean
} = {}
) => {
const { mode = 'push', transform = (value) => value, isEncodeURIComponent = false } = options
const route = useRoute()
const router = useRouter()
const currentPath = route.path
const query = ref(defaultValue) as Ref<T | K>
watch(
() => route.query[name],
(value) => {
// 不是当前页面时不更新
if (route.path !== currentPath) {
return
}
if (value === undefined) {
query.value = defaultValue as T
return
}
if (!isEncodeURIComponent) {
query.value = transform(value)
return
}
query.value = transform(decodeURIComponent(value as string))
},
{ immediate: true })
watch(query, (value) => {
const { params, query: oldQuery, hash } = route
router[mode]({
params,
query: {
...oldQuery,
[name]: isEncodeURIComponent ? encodeURIComponent(value as string) : value
},
hash
})
})
return query
}
实际使用过后,我们会发现以上实现存在一些问题:
-
多个变量同步修改时,
route.query
上只会保留最后一个修改的响应式变量,代码如下;import { useRouteQuery } from '@/hooks/useRouteQuery' const disabled = useRouteQuery('disabled', 0) const title = useRouteQuery('title', '') disabled.value = 1 title.value = 'test' // 预想:?disabled=1&title=test // 实际:?title=test
-
特殊值
undefined
、null
经过encodeURIComponent
处理后会转换为字符串格式,因此还会带在route.query
上,如“?diabled=null&title=undefined”。
2. 完整实现
解决方案:
- 通过在函数外定义一个队列来保存所有需要更新的值,并异步更新
route.query
; - 特殊值不使用
encodeURIComponent
方法转义。
import { nextTick, watch, ref, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
type IQuery = string | number | string[] | null | undefined
// 用来保存所有的query
const queriesQueue = new Map<string, Record<string, IQuery>>()
/**
* 获取当前页面的query
* @param name
* @param defaultValue
* @param options
* @returns
*/
export const useRouteQuery = <T extends IQuery, K extends IQuery = T>(
name: string,
defaultValue?: T,
options: {
transform?: (value: any) => K
mode?: 'push' | 'replace'
isEncodeURIComponent?: boolean
} = {}
) => {
const { mode = 'push', transform = (value) => value, isEncodeURIComponent = false } = options
const route = useRoute()
const router = useRouter()
const currentPath = route.path
const query = ref(defaultValue) as Ref<T | K>
watch(
() => route.query[name],
(value) => {
if (route.path !== currentPath) {
return
}
if (value === undefined) {
query.value = defaultValue as T
return
}
if (!isEncodeURIComponent) {
query.value = transform(value)
return
}
query.value = transform(decodeURIComponent(value as string))
},
{ immediate: true })
const setQueryQueue = (value: IQuery) => {
const currentPageQueries = queriesQueue.get(currentPath) || {}
// 特殊值不转义
if (value === null || value === undefined) {
currentPageQueries[name] = value
} else {
currentPageQueries[name] = isEncodeURIComponent ? encodeURIComponent(value as string) : value
}
queriesQueue.set(currentPath, currentPageQueries)
}
watch(query, (value) => {
setQueryQueue(value as IQuery)
// 异步更新
nextTick(() => {
// 获取当前页面所有的query
const currentPageQueries = queriesQueue.get(currentPath) || {}
const { params, query: oldQuery, hash } = route
router[mode]({
params,
query: {
...oldQuery,
...currentPageQueries
},
hash
})
})
})
return query
}
查看完整实现源码。
原文链接:https://juejin.cn/post/7343835639990927396 作者:求知若饥