页面跳转传参最佳实践:大对象别再塞 Query 了
路由一跳,参数一长,味道就不对了。
我见过最离谱的一次,是把一个几千项的商品数组直接塞进 query,地址栏跟火车一样长。刷新一下丢了,复制链接发给别人还带着半截 %5B%7B%22id%22。这类问题,表面看是"页面跳转怎么传参",真到线上,实际是在问:大对象到底该放哪,谁来兜底,刷新怎么办。
先说一个我不太建议的姿势
很多人第一反应是:
router.push({
path: '/confirm',
query: {
data: JSON.stringify(bigList)
}
})这玩意小数据能跑,大一点就开始冒汗。
第一,URL 有长度限制,不同浏览器、不同代理层都不一样,你不能拿它赌。第二,JSON.stringify 后字符全挤在地址栏里,既不好看也不安全。第三,用户一刷新、转发、回退,排查起来很烦,因为你根本不知道是编码丢了,还是参数被截了。
这种方案我一般只拿来传几个筛选条件,不拿来扛大数组。
真正稍微稳一点的做法,通常是三种
方式一:路由只传一个 key,数据落在 sessionStorage
这招我平时用得很多,尤其是"列表页选中一批数据,跳确认页"这种场景。因为这类数据本来就是一次性会话数据,浏览器标签页关了就算,放 sessionStorage 挺顺。
function jumpWithSessionData(path, payload) {
const cacheKey = `jump:${Date.now()}:${Math.random().toString(16).slice(2)}`
sessionStorage.setItem(cacheKey, JSON.stringify(payload))
router.push({
path,
query: {
cacheKey
}
})
}目标页拿数据:
function readJumpData() {
const { cacheKey } = router.currentRoute.value.query
if (!cacheKey) return null
const raw = sessionStorage.getItem(cacheKey)
if (!raw) return null
try {
const data = JSON.parse(raw)
sessionStorage.removeItem(cacheKey) // 用完就删,别堆垃圾
return data
} catch (e) {
console.error('跳转数据解析失败', e)
return null
}
}这个方案的好处很直接:URL 干净,数据量比 query 能扛得多,代码也不绕。
但它不是没坑。刷新页面还能拿到,因为 sessionStorage 还在;你把链接复制给别人,对方打不开原数据,因为对方浏览器里没有那份缓存。这一点要提前想明白。也就是说,它适合"当前用户自己跳过去继续操作",不适合"分享链接即恢复现场"。
方式二:数据放状态管理(store)
如果这份数据本来就是多个页面都会用,或者用户在当前流程里反复来回切,我更倾向直接放状态管理,不走"传大对象"这条路。
// store/orderDraft.js
import { defineStore } from 'pinia'
export const useOrderDraftStore = defineStore('orderDraft', {
state: () => ({
selectedRows: [],
extraForm: {}
}),
actions: {
setDraft(rows, form) {
this.selectedRows = rows
this.extraForm = form
},
clearDraft() {
this.selectedRows = []
this.extraForm = {}
}
}
})跳转前:
const draftStore = useOrderDraftStore()
function goConfirmPage(rows, form) {
draftStore.setDraft(rows, form)
router.push('/confirm')
}确认页直接取:
const draftStore = useOrderDraftStore()
onMounted(() => {
if (!draftStore.selectedRows.length) {
console.warn('确认页缺少草稿数据')
router.replace('/list')
}
})这个方式代码最干净,尤其适合 Vue/React 这种单页应用。问题也明显:页面一刷新,内存里的状态可能没了。这个时候别硬扛,要么配持久化插件,要么就老老实实落 sessionStorage。
方式三:服务端临时落库,前端只传 token 或 id
这个办法最稳,也最适合大对象真的很大、还要支持分享、刷新恢复、多端继续的时候。比如导出任务、复杂表单草稿、批量对账结果,这些数据本来就不该全塞浏览器。
async function createJumpTicket(payload) {
const res = await fetch('/api/temp-data/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
const { ticket } = await res.json()
router.push({
path: '/result',
query: { ticket }
})
}目标页再按 ticket 拉取:
async function loadByTicket(ticket) {
const res = await fetch(`/api/temp-data/detail?ticket=${ticket}`)
if (!res.ok) throw new Error('临时数据读取失败')
return await res.json()
}这招的好处是,浏览器轻松,链接还能分享,刷新也不怕。代价就是你后端得配合,还得处理过期时间、清理策略、权限校验。这地方别偷懒,尤其 ticket 如果能猜出来,等于把别人的数据挂门口了。
场景决定方案
所以这事别问"哪种最优雅",要先问场景。
当前页跳下一页,数据只给当前用户临时用一次:sessionStorage + key,够了。
数据是流程态,多个页面都会改,还要频繁回退前进:store 顶上。
数据很大,还要求刷新不丢、链接可打开、甚至能跨端继续:直接上服务端临时存储,前端只带 id。
一个顺手的小封装
最后给一个我自己更顺手的封装,现场里拿来就能用:
export function usePageJumpCache(prefix = 'pagecache') {
function save(data) {
const key = `${prefix}:${Date.now()}:${Math.random().toString(36).slice(2)}`
sessionStorage.setItem(key, JSON.stringify(data))
return key
}
function read(key, remove = true) {
const raw = sessionStorage.getItem(key)
if (!raw) return null
try {
const data = JSON.parse(raw)
if (remove) sessionStorage.removeItem(key)
return data
} catch (e) {
console.error('缓存解析失败', key, e)
return null
}
}
return { save, read }
}跳转问题,很多时候不是"怎么把 Object 带过去",而是别把不该放在 URL 里的东西,硬往 URL 里塞。
你真把这个判断顺序理清了,后面代码反而没什么玄学:小参数走 query,会话态走 sessionStorage,流程态走 store,可恢复的大数据走服务端。别一上来就 JSON.stringify 全塞地址栏,那玩意我第一眼就不太信。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!