Code前端首页关于Code前端联系我们

Vue Router怎么处理query里的数组?

terry 7小时前 阅读数 9 #Vue

做前端项目时,经常遇到要把数组数据通过路由参数传递的情况——比如多标签筛选、多选项表单的状态同步,刷新页面后还得保留这些筛选条件,这时候就绕不开一个问题:Vue Router 里的 query 参数怎么处理数组?今天就从场景、用法、踩坑到优化,一步步把这件事讲清楚。

为什么要在query里传数组?

先想清楚“为啥非得用query传数组”,路由传参分paramsqueryparams是“路径参数”(比如/user/:id),刷新会丢;query是“查询参数”(URL里后面的部分),刷新能保留,还能被收藏、分享。

实际开发中,这些场景特别常见:

  • 多条件筛选:比如电商列表页,用户选了“男装、夏季、折扣”三个标签,把这三个标签存在数组里,通过query传给列表页,这样刷新页面、分享链接时,筛选条件不会丢。
  • 多选项表单:比如注册页有“爱好”多选框(唱歌、跳舞、游泳),提交前要把爱好数组通过query暂存到下一步,避免页面跳转后数据丢失。
  • 页面状态同步:比如tabs组件的激活标签数组,通过query存起来,不同设备打开同一个链接时,tabs状态能一致。

要是不用query传数组,要么得把数组存在Vuex或Pinia里(刷新就丢,还得结合localStorage做持久化,麻烦);要么用params传(刷新丢数据,分享链接也带不上参数),所以用query传数组,是“保留状态+支持分享”的最优解之一。

Vue Router中query传数组的基本用法

先看怎么把数组塞进query,Vue Router的router.push(或router.replace)支持传对象,里面的query字段可以直接放数组:

// 组件里调用路由跳转
import { useRouter } from 'vue-router'
const router = useRouter()
// 假设要传的标签数组是 ['vue', 'js', 'router']
const tags = ['vue', 'js', 'router']
router.push({
  path: '/article-list',
  query: { tags: tags } // 也可以简写成 { query: { tags } }
})

跳转后,URL会变成啥样?比如原来的基础路径是http://xxx.com,跳转后URL会是http://xxx.com/article-list?tags=vue&tags=js&tags=router——Vue Router会自动把数组里的每个元素,转成同名的query参数(即多个tags=)。

接收端怎么拿这个数组?用useRoute(Vue 3组合式API)或者this.$route(Vue 2选项式API)获取query:

// Vue 3 组合式API
import { useRoute } from 'vue-router'
const route = useRoute()
const receivedTags = route.query.tags
console.log(receivedTags) 
// 打印结果:['vue', 'js', 'router'](当URL里有多个tags参数时)

这里有个细节:如果数组只有一个元素,比如tags: ['vue'],URL会是?tags=vue,这时候route.query.tags拿到的是字符串'vue',而不是数组!这就会导致“单元素数组变字符串”的问题,后面讲踩坑时会重点解决。

刷新页面后,query数组“变形”或丢失?

很多同学遇到过:跳转时数组传得好好的,一刷新页面,route.query.tags要么变成字符串,要么直接丢了,这得从URL解析逻辑前端处理容错两方面分析。

问题根源:URL参数的“单/多值”歧义

URL里的参数本质是“键值对”,但同一个键可以有多个值(比如?a=1&a=2),不同环境对这种“多值参数”的解析规则不同:

  • Vue Router(前端):如果URL里有多个同名参数(如tags=vue&tags=js),route.query.tags会被解析成数组;但如果只有一个tags=vue,就会解析成字符串
  • 服务端(比如Node.js的Express,或Nginx转发):对多值参数的解析逻辑可能和前端不同,比如有的服务端框架会把?tags=vue&tags=js解析成{ tags: ['vue', 'js'] },但也有框架会解析成{ tags: 'js' }(取最后一个值)——这会导致服务端渲染(SSR)时数据不一致。

解决方案:统一“数组化”处理

不管是前端刷新,还是服务端渲染,都要保证拿到query.tags后,强制转成数组,可以写个工具函数:

// 工具函数:把query里的某个参数转成数组
function getQueryArray(routeQuery, key) {
  const value = routeQuery[key]
  // 如果是数组,直接返回;如果是字符串,包成数组;如果undefined,返回空数组
  if (Array.isArray(value)) return value
  if (typeof value === 'string') return [value]
  return []
}
// 使用示例
const tags = getQueryArray(route.query, 'tags')

在Vue组件里,也可以用computed做“双向”处理(既保证获取时是数组,又能在修改时更新query):

// Vue 3 组合式API示例
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const tags = computed({
  get() {
    const raw = route.query.tags
    return Array.isArray(raw) ? raw : (raw ? [raw] : [])
  },
  set(newTags) {
    // 跳转时,把新数组塞回query,同时保留其他query参数
    router.push({
      query: { ...route.query, tags: newTags }
    })
  }
})

这样不管URL里tags是单值还是多值,tags这个computed始终返回数组;修改tags时,也会自动更新URL的query参数。

如何优雅封装query数组的处理逻辑?

如果项目里很多地方要传数组到query,重复写“转数组、处理刷新”的逻辑太冗余,这时候可以封装成工具库自定义组合式API,减少重复代码。

方案1:写全局工具函数

把“解析数组”和“更新数组到query”的逻辑封装成函数,

// utils/router-query.js
import { useRouter, useRoute } from 'vue-router'
/**
 * 获取query中的数组参数
 * @param {string} key - 参数名
 * @returns {array} 处理后的数组
 */
export function useQueryArray(key) {
  const route = useRoute()
  const router = useRouter()
  const getArray = () => {
    const raw = route.query[key]
    return Array.isArray(raw) ? raw : (raw ? [raw] : [])
  }
  const setArray = (newArr) => {
    router.push({
      query: { ...route.query, [key]: newArr }
    })
  }
  return [getArray, setArray]
}

组件里用的时候,像这样:

// 组件中使用
import { useQueryArray } from '@/utils/router-query.js'
const [getTags, setTags] = useQueryArray('tags')
// 获取数组
const tags = getTags()
// 更新数组到query
setTags(['new', 'tags'])

方案2:结合Pinia做状态管理

如果项目用了Pinia,可以把query数组的“读写逻辑”放到Store里,让组件更干净:

// stores/query.js
import { defineStore } from 'pinia'
import { useRouter, useRoute } from 'vue-router'
export const useQueryStore = defineStore('query', () => {
  const router = useRouter()
  const route = useRoute()
  // 获取query数组
  function getArray(key) {
    const raw = route.query[key]
    return Array.isArray(raw) ? raw : (raw ? [raw] : [])
  }
  // 设置query数组
  function setArray(key, newArr) {
    router.push({
      query: { ...route.query, [key]: newArr }
    })
  }
  return { getArray, setArray }
})

组件里调用:

import { useQueryStore } from '@/stores/query.js'
const queryStore = useQueryStore()
const tags = queryStore.getArray('tags')
queryStore.setArray('tags', ['vue', 'js'])

这样做的好处是:所有和query数组相关的逻辑都集中在Store里,组件只负责调用,后期修改解析规则(比如要支持JSON.stringify的数组)也只需要改Store里的代码。

结合Vue 3 + Composition API的实践细节

Vue 3的组合式API让代码更灵活,但处理query数组时,还有些细节要注意:

响应式更新的时机

useRoute返回的route是响应式的,但如果在onMounted里直接取route.query.tags,后续query变化时,不会自动触发组件更新,所以必须用computed或watch来监听query变化

比如前面的computed示例,就是利用computed的响应式特性,让tags始终和route.query.tags同步。

处理空数组的情况

如果数组是空的(比如用户清空了所有筛选标签),要确保URL里的tags参数被清除,否则URL会残留?tags=(空字符串),所以在setArray时,要判断数组是否为空:

// 改进setArray逻辑:空数组时删除query参数
function setArray(key, newArr) {
  const newQuery = { ...route.query }
  if (newArr.length === 0) {
    delete newQuery[key] // 空数组时,从query里删掉这个参数
  } else {
    newQuery[key] = newArr
  }
  router.push({ query: newQuery })
}

这样URL里就不会出现?tags=这种无效参数,更简洁。

常见错误与解决方案

处理query数组时,这些“坑”很容易踩,提前避坑能省很多调试时间:

错误1:URL里出现tags[]=xxx这种格式

原因:如果项目里用了qs库(比如Axios默认带qs,或者自己配置了router.options.parseQuery用qs解析),qs会把数组转成tags[]=vue&tags[]=js这种“PHP风格”的参数。

解决:Vue Router默认用的是原生URLSearchParams解析query,不需要额外配置qs,如果项目里强制用了qs,要修改解析/序列化规则:

// 路由配置中,覆盖parseQuery和stringifyQuery
const router = createRouter({
  history: createWebHistory(),
  routes: [...],
  parseQuery: (query) => {
    // 用qs解析,但配置arrayFormat为重复(重复key)
    return qs.parse(query, { arrayFormat: 'repeat' })
  },
  stringifyQuery: (query) => {
    // 用qs序列化,同样配置arrayFormat为重复
    return qs.stringify(query, { arrayFormat: 'repeat' })
  }
})

这样qs就会把数组转成tags=vue&tags=js而不是tags[]=xxx

错误2:数组元素含特殊字符,导致URL解析错误

比如数组元素是'vue&js',直接传的话,URL会变成?tags=vue&js(把&当参数分隔符了),导致参数解析错误。

解决:传参前编码,接收后解码,用encodeURIComponentdecodeURIComponent

// 传参时编码每个元素
const encodedTags = tags.map(tag => encodeURIComponent(tag))
router.push({ query: { tags: encodedTags } })
// 接收时解码
const decodedTags = getQueryArray(route.query, 'tags').map(tag => decodeURIComponent(tag))

或者在工具函数里统一处理编码/解码,避免每个组件重复写。

错误3:多次跳转导致路由重复(NavigationDuplicated)

连续快速点击“筛选”按钮,多次调用router.push传相同的数组,Vue Router会抛出NavigationDuplicated错误(因为要跳转到相同的URL)。

解决:用router.replace代替router.push(替换当前历史记录,不会重复),或者在跳转前判断参数是否变化:

// 跳转前判断:如果新数组和当前query里的数组相同,就不跳转
const currentTags = getQueryArray(route.query, 'tags')
if (JSON.stringify(newTags) !== JSON.stringify(currentTags)) {
  router.push({ query: { ...route.query, tags: newTags } })
}

服务端渲染(SSR)下的特殊处理

如果项目用Nuxt.js、Vue SSR等服务端渲染方案,要注意服务端和客户端对query数组的解析必须一致,否则会出现“ hydration mismatch”(服务端和客户端渲染结果不一致)的错误。

核心思路:服务端也用同样的“数组化”逻辑

比如在Nuxt.js的页面组件中,服务端获取query时,也要用和客户端一样的getQueryArray函数:

// Nuxt 3 页面组件示例
export default definePageComponent({
  async asyncData({ route }) { // 服务端获取route
    const tags = getQueryArray(route.query, 'tags')
    return { tags }
  },
  setup() {
    const route = useRoute()
    const tags = computed(() => getQueryArray(route.query, 'tags'))
    // ... 其他逻辑
  }
})

这样服务端和客户端都用getQueryArray处理tags,保证两端数据结构一致,避免 hydration 错误。

性能与SEO:query数组要注意什么?

虽然query数组能解决状态保留问题,但URL参数太多、太长,会影响性能和SEO:

URL长度限制

不同浏览器对URL长度的限制不同(一般2000字符左右),如果数组元素多、每个元素长,很容易超过限制,导致URL被截断,参数丢失。

建议:

  • 数组元素尽量“轻量化”:比如用枚举值(tag1代替前端框架Vue)。
  • 必要时做“参数压缩”:比如把数组转成Base64编码(但要注意解码性能)。

SEO友好性

搜索引擎更青睐“语义化、简洁”的URL,如果query数组是“筛选条件”这类对用户/SEO有意义的参数,问题不大;但如果是无意义的临时状态(比如表单步骤),建议用params或Store暂存,别放query里。

query数组的最佳实践

把前面的知识点提炼成“步骤式”指南,方便记忆:

  1. 传参时:直接在router.pushquery里放数组,Vue Router会自动转成多同名参数(如tags=vue&tags=js)。
  2. 接收时:用工具函数/computed把route.query.key强制转成数组(处理单元素变字符串、undefined的情况)。
  3. 封装逻辑:把“解析+更新”逻辑封装成工具函数或Pinia Store,减少重复代码。
  4. 避坑处理:编码特殊字符、处理空数组、避免路由重复跳转、统一SSR和客户端的解析逻辑。
  5. 性能与SEO:控制URL长度,让参数有语义。

处理Vue Router的query数组,核心是理解“URL参数的多值解析规则”和“前端-服务端的一致性”,只要把传参、解析、封装、避坑这几个环节理清楚,不管是多标签筛选还是页面状态同步,都能轻松实现,下次遇到类似需求,直接套这套思路,少走弯路~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门