useRouteQuery自实现

本文将使用vue3ts从零实现一个类vueuseuseRouteQuery方法,接受基本相同的参数(移除了router与route参数),并解决vueuseuseRouteQuery方法存在的一些问题。

使用 vueuse 的 useRouteQuery 碰到的问题

问:为什么不使用vueuse的提供的useRouteQuery方法?

答:因为在使用<KeepAlive>保活的页面级组件之间切换时,在所有组件中使用vueuseuseRouteQuery方法定义的变量都会更新。

例如:打开A页面(保活)后,修改page为2,假设url现在为/pageA?page=2,然后切换到B页面,A页面将会触发watchpage将会更新为默认值,并且每次修改B页面的query都会触发A页面的query更新。代码如下。

ts 复制代码
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)
})

查看vueuseuseRouteQuery的源码后发现它是根据router对象去保存query信息的,因此每次urlquery变化时,所有已存在的相关变量都会更新。

从零实现一个 useRouteQuery

思考:

  1. 监听route.query的变化,在值变化时更新响应式变量的值;
  2. 监听响应式变量的变化,在值变化时修改route.query的值;
  3. route.query变化时,判断是否是当前页面,不是则跳过更新过程。

1. 简易实现

ts 复制代码
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上只会保留最后一个修改的响应式变量,代码如下;

    ts 复制代码
    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
  • 特殊值undefinednull经过encodeURIComponent处理后会转换为字符串格式,因此还会带在route.query上,如"?diabled=null&title=undefined"。

完整实现

解决方案:

  1. 通过在函数外定义一个队列来保存所有需要更新的值,并异步更新route.query
  2. 特殊值不使用encodeURIComponent方法转义。
ts 复制代码
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
}

查看完整实现源码

相关推荐
bin91533 分钟前
前端JavaScript导出excel,并用excel分析数据,使用SheetJS导出excel
前端·javascript·excel
陈小白_weilin8 分钟前
VUE-CLI配置全局SCSS变量
vue.js·scss
Rattenking11 分钟前
node - npm常用命令和package.json说明
前端·npm·json
Easonmax12 分钟前
【HTML5】html5开篇基础(1)
前端·html·html5
For. tomorrow16 分钟前
Vue3中el-table组件实现分页,多选以及回显
前端·vue.js·elementui
布瑞泽的童话42 分钟前
无需切换平台?TuneFree如何搜罗所有你爱的音乐
前端·vue.js·后端·开源
白鹭凡1 小时前
react 甘特图之旅
前端·react.js·甘特图
打野赵怀真1 小时前
你有看过vue的nextTick源码吗?
前端·javascript
2401_862886781 小时前
蓝禾,汤臣倍健,三七互娱,得物,顺丰,快手,游卡,oppo,康冠科技,途游游戏,埃科光电25秋招内推
前端·c++·python·算法·游戏