news 2026/8/14 5:41:04

Vue 3 路由实战:新窗口打开、Router/Route 与 Query/Params 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue 3 路由实战:新窗口打开、Router/Route 与 Query/Params 详解

1. 从一次紧急需求说起:为什么新窗口打开页面不是小事

那天下午,产品经理急匆匆地跑过来,说有个客户反馈,在填写一个长达十几页的复杂表单时,不小心点了浏览器后退键,半小时的心血瞬间清零,客户差点“原地爆炸”。他们希望能在填写过程中,随时点击一个“查看填写指引”的链接,在一个独立的新窗口打开帮助文档,这样主表单页面的状态就能完全保留。这个需求听起来简单,不就是个target="_blank"吗?但当我真正在 Vue 3 项目里动手时,才发现这里面的水,比想象中要深。

首先,在 Vue 的单页应用(SPA)架构里,传统的<a href="..." target="_blank">会触发完整的页面重载,这违背了 SPA 流畅体验的初衷。其次,Vue Router 管理的路由跳转,默认都是在当前窗口(或标签页)内进行的组件切换。如何优雅地、符合 Vue 生态的方式打开新窗口,并且还能携带参数、管理状态,就成了一个需要仔细权衡的技术点。

更让我头疼的是,在实现过程中,我频繁地与routerroute这两个长得像双胞胎似的对象打交道,传参时又在queryparams之间反复横跳,一不小心就踩坑。我相信很多从 Vue 2 过渡到 Vue 3 的开发者,或者 Vue 3 的新手,都曾在这几个概念上迷糊过。它们看似基础,却直接影响着路由功能的正确实现和代码的可维护性。

所以,今天我就结合那次“救火”经历和后续的复盘,把这几个问题彻底讲透。我会先拆解在 Vue 3 中实现新窗口打开的三种主流方式及其适用场景,然后深入辨析routerroute这对核心对象,最后厘清queryparams这两种传参方式的本质区别与选用策略。这些内容都是你构建健壮前端路由时不可或缺的“内功心法”。

2. Vue 3 中打开新窗口的三种实战方案

实现新窗口打开,远不止一种方法。每种方法背后都有其设计意图和适用边界,选错了可能就会导致状态丢失、样式错乱或者路由守卫失效。下面我结合代码和实际场景,带你逐一分析。

2.1 方案一:使用原生 window.open 与路由解析

这是最直接、最底层的方式。它不依赖于 Vue Router 的导航方法,而是直接调用浏览器 BOM API。

// 在组件方法或组合式 API 中 const openNewWindow = () => { // 1. 手动构建完整的 URL const newWindow = window.open('', '_blank'); // 2. 使用 Vue Router 的 resolve 方法解析路由(推荐) import { useRouter } from 'vue-router'; const router = useRouter(); const resolvedRoute = router.resolve({ name: 'HelpDocument', // 使用命名路由更可靠 query: { section: 'form-guide' } }); // 3. 将解析后的完整 href 赋值给新窗口 if (newWindow) { newWindow.location.href = resolvedRoute.href; } else { // 处理浏览器可能阻止弹窗的情况 console.error('新窗口被浏览器阻止,请检查弹出窗口拦截设置或用户手势。'); } };

为什么选择router.resolve()关键就在这里。router.resolve()是 Vue Router 提供的一个方法,它根据你传入的路由配置(路径、参数等),计算出一个包含完整路径 (href)、目标路由对象 (route) 和导航守卫钩子 (navigation) 等信息的对象。使用它,而不是自己拼接字符串,有两大好处:

  1. 保证一致性:无论你的路由模式是history还是hash,基础路径 (base) 是什么,resolve()都能生成正确的 URL,避免了手动拼接可能导致的路径错误。
  2. 利用路由配置:它尊重你在路由表中为‘HelpDocument’路由设置的所有规则。

注意:现代浏览器出于用户体验和安全考虑,对非用户直接触发的window.open(比如在异步请求回调中调用)可能会进行拦截。最佳实践是确保window.open的调用直接源自一次用户点击事件(如@click)。

适用场景

  • 需要完全控制新窗口的打开时机和方式。
  • 目标页面是一个独立的、无需与父窗口通信的 SPA 或静态页面。
  • 作为兜底方案,当其他更集成化的方案遇到兼容性问题时。

2.2 方案二:借助<router-link>的 target 属性

如果你要打开的链接就是一个普通的 Vue 路由,并且希望保持声明式的代码风格,那么<router-link>target="_blank"属性是你的首选。

<template> <!-- 声明式导航,打开帮助文档到新窗口 --> <router-link :to="{ name: 'HelpDocument', query: { from: 'complex-form' } }" target="_blank"> 在新窗口打开帮助文档 </router-link> </template>

这种方式极其简洁。Vue Router 会内部处理target="_blank"的行为,它会阻止默认的 SPA 导航,转而使用类似方案一的方式(但经过框架封装)打开新窗口。

背后的原理与坑点: 当你添加target="_blank"后,<router-link>组件实质上渲染为一个普通的<a>标签,并设置了href属性为解析后的路由 URL。点击时,浏览器的默认行为被触发,在新标签页加载该 URL。这意味着:

  1. 全新的 Vue 应用实例:新窗口将启动一个独立的 Vue 应用,完全独立于原窗口。两者之间的数据状态(如 Pinia/Vuex store)、组件实例不共享
  2. 路由守卫会执行:因为新窗口是从头开始加载你的 SPA,所以目标路由对应的全局前置守卫 (beforeEach)、独享守卫 (beforeEnter) 都会正常触发。
  3. 注意样式和脚本加载:确保你的应用能正确处理完整的页面加载,而非仅 SPA 内的组件切换。

适用场景

  • 静态的、声明式的导航链接。
  • 目标页面是当前 SPA 的一部分,且不需要与源页面保持复杂的实时状态同步。
  • 追求代码简洁和模板可读性。

2.3 方案三:编程式导航结合 window.open

这是方案一和 Vue Router 编程式导航的混合体,也是我在处理那个复杂表单需求时最终采用的方案。它更灵活,允许你在导航前后执行自定义逻辑。

import { useRouter } from 'vue-router'; const router = useRouter(); const openGuideInNewWindow = async () => { // 1. 可选:在导航前执行一些逻辑,例如保存当前表单的草稿 await saveFormDraft(); // 2. 使用 router.resolve 解析目标路由 const resolved = router.resolve({ path: '/help/doc', query: { formId: currentFormId.value, timestamp: Date.now() // 避免缓存 } }); // 3. 打开新窗口 const newWin = window.open(resolved.href, '_blank', 'width=1000,height=700'); // 4. 可选:与新窗口进行简单通信(需同源) if (newWin) { // 设置一个简单的消息监听器(示例) const receiveMessage = (event) => { if (event.data === 'guide_closed' && event.origin === window.location.origin) { console.log('帮助窗口已关闭,可以刷新指引状态'); // 执行一些回调逻辑... } }; window.addEventListener('message', receiveMessage); // 也可以向新窗口传递初始数据(需新窗口配合监听) // setTimeout(() => { // newWin.postMessage({ action: 'init', data: someData }, window.location.origin); // }, 500); } };

这种方式的优势在于控制力

  • 逻辑前置:你可以在打开新窗口前进行数据保存、验证或日志记录。
  • 参数动态化:可以非常方便地根据当前组件状态动态构造query参数。
  • 有限的通信:通过postMessageAPI,可以在同源策略允许下,实现父子窗口间的简单数据传递。这对于我当时的场景很有用——当用户在新窗口看完指引后,父窗口可以更新一个“已阅读”的状态标识。

一个关键的细节:路由模式与 Base URL无论你用方案一还是方案三,只要涉及手动构建或解析 URL,就必须考虑你的路由模式。如果你的项目部署在子路径下(例如https://domain.com/my-app/),并且使用了history模式,那么router.resolve()会自动帮你处理好基础路径。但如果你自己拼接字符串,很容易漏掉这个/my-app/前缀,导致 404 错误。

适用场景

  • 导航前需要执行异步操作(如保存数据)。
  • 需要向新窗口传递复杂的、动态生成的参数或状态。
  • 希望在新窗口关闭后,在原始窗口执行回调逻辑。
  • 需要更精细地控制新窗口的特性(如尺寸、位置)。

3. 深入核心:Router 与 Route 的本质区别

解决了“开窗”问题,我们再来啃啃routerroute这块硬骨头。在 Vue Router 4(Vue 3 配套版本)中,它们是通过组合式 APIuseRouter()useRoute()返回的两个不同对象。很多新手容易混淆,因为它们看起来都包含着“路由”信息。

3.1 Router:导航的指挥官

你可以把router想象成整个路由系统的指挥官、导航器。它的核心职责是控制路由的跳转

import { useRouter } from 'vue-router'; const router = useRouter(); // 1. 跳转到指定路径(编程式导航) router.push('/home'); router.push({ name: 'UserProfile', params: { userId: 123 } }); // 2. 替换当前历史记录(不会留下后退记录) router.replace('/login'); // 3. 在历史记录中前进或后退 router.go(1); // 前进一页 router.go(-2); // 后退两页 // 4. 添加、移除导航守卫(全局的“关卡”) router.beforeEach((to, from, next) => { // 权限检查逻辑 next(); }); // 5. 解析路由,获取其完整信息(如前文所用) const resolved = router.resolve({ name: 'About' });

关键记忆点router主动的,你用它来发起动作(去哪),它是一个方法集合全局管理器。在组件中,你通常用它来触发页面跳转。

3.2 Route:当前地址的快照

route对象,则是当前活跃路由状态的一个只读快照。它告诉你现在在哪里

import { useRoute } from 'vue-router'; const route = useRoute(); // 访问当前路由的信息 console.log(route.path); // 当前路径,如 "/user/123/profile" console.log(route.name); // 当前路由名称,如 "UserProfile" console.log(route.params); // 动态路径参数对象,如 { userId: "123" } console.log(route.query); // URL 查询参数对象,如 { tab: 'settings' } console.log(route.hash); // URL 的 hash 部分,如 "#section-2" console.log(route.fullPath); // 完整解析的 URL,包含查询参数和 hash console.log(route.matched); // 一个数组,包含与当前路由匹配的所有路由记录(用于嵌套路由)

关键记忆点route被动的,你用它来读取信息(在哪),它是一个数据对象,其内容会随着 URL 的变化而自动响应式更新。在组件中,你通常用它来获取当前页面的参数、根据参数 fetch 数据。

3.3 一个常见的混淆场景与正确做法

假设你在一个用户详情页组件 (UserDetail.vue) 里,需要根据userId参数获取数据,并且在某个按钮点击后跳转到该用户的编辑页面。

错误或别扭的写法

// 组合式 API setup 中 const router = useRouter(); const route = useRouter(); // 错误!这里本意是想用 useRoute const userId = router.params.userId; // 错误!router 对象上没有 params 属性 const goToEdit = () => { route.push(`/user/${userId}/edit`); // 错误!route 对象上没有 push 方法 };

正确的写法

import { useRouter, useRoute } from 'vue-router'; const router = useRouter(); // 用于导航跳转 const route = useRoute(); // 用于获取当前路由信息 // 从当前路由的快照中获取参数 const userId = route.params.userId; // 使用 router 指挥官进行跳转 const goToEdit = () => { // 方法1: 使用路径 router.push(`/user/${userId}/edit`); // 方法2: 使用命名路由和 params (更推荐,与路由表解耦) router.push({ name: 'UserEdit', params: { userId: userId } }); };

一句话总结:想去哪,找router;想知道现在在哪,看route。这个区分是理解 Vue Router 编程式导航的基础。

4. 传参的艺术:Query 与 Params 的深度对比

在路由跳转时传递参数,queryparams是两种主要方式。选择哪一种,不仅影响 URL 形态,更关系到数据传递的语义、可用性和刷新行为。

4.1 Query 参数:灵活的可选过滤器

query参数以 URL 查询字符串的形式出现,跟在?后面,格式为key=value,多个参数用&连接。

定义与使用: 在路由表中,query参数无需预先声明

// 路由跳转 router.push({ path: '/search', query: { keyword: 'vue', page: 2, sort: 'desc' } }); // 生成的 URL: /search?keyword=vue&page=2&sort=desc // 在目标组件中读取 const route = useRoute(); console.log(route.query.keyword); // 'vue' console.log(route.query.page); // '2' (注意:所有 query 值都是字符串)

核心特性与适用场景

  1. 可选性与灵活性query非常适合传递可选的、过滤性的参数。比如搜索关键词、分页页码、排序方式。这些参数可以有默认值,用户是否提供都不影响路由本身的匹配。
  2. 语义清晰/products?category=electronics&priceRange=100-500这样的 URL,对人类和搜索引擎都更友好,清晰地表达了“正在查看电子产品中价格在100到500之间的商品”。
  3. 刷新保留query参数完全体现在 URL 中,因此页面刷新后参数不会丢失,便于分享和书签保存。
  4. 类型为字符串:从route.query中取出的所有值都是字符串类型。如果你需要数字或布尔值,需要手动转换,例如const page = Number(route.query.page) || 1

4.2 Params 参数:路径的一部分

params参数是路径本身的一部分,用于匹配路由模式中动态的片段。

定义与使用: 必须在路由表中预先定义占位符。

// 路由定义 { path: '/user/:userId/profile/:tab?', // :userId 是必需参数,:tab? 是可选参数 name: 'UserProfile', component: UserProfile } // 路由跳转 (使用命名路由是传递 params 最可靠的方式) router.push({ name: 'UserProfile', params: { userId: 123, tab: 'settings' } }); // 生成的 URL: /user/123/profile/settings // 如果使用 path,则 params 会被忽略(这是一个大坑!) router.push({ path: `/user/${123}/profile`, params: { tab: 'settings' } }); // params 无效! // 正确做法:将 tab 也拼接到 path 中,或者使用 name。 // 在目标组件中读取 const route = useRoute(); console.log(route.params.userId); // '123' (同样是字符串) console.log(route.params.tab); // 'settings'

核心特性与适用场景

  1. 结构性与必需性params用于标识资源的核心身份或层级结构。如/user/123中的123标识了唯一的用户,它是视图的核心标识,通常是必需的。
  2. 更简洁的 URL:对于标识性参数,将其放在路径中比放在查询字符串里看起来更干净、更 RESTful。
  3. 刷新保留:同样,因为它是 URL 的一部分,刷新后也会保留。
  4. path属性的冲突:这是最大的坑!当你在router.push<router-link :to>中提供了path属性时,任何params属性都会被忽略。因此,使用params时,强烈推荐配合命名路由(name) 一起使用,这是最安全、最可靠的方式。
  5. 可选参数:可以通过在占位符后加?来定义可选参数(如:tab?),但使用时仍需注意上述规则。

4.3 对比表格与选型指南

特性Query (query)Params (params)
URL 中的位置?之后,查询字符串/分隔的路径的一部分
路由定义无需预先声明必须在路由path中声明(如:id
传递方式可与pathname一起使用强烈建议name一起使用,与path同用会被忽略
语义可选参数、过滤条件、搜索项资源标识、视图的必需或核心参数
数据类型所有值均为字符串所有值均为字符串
刷新行为保留保留
示例URL/products?category=book&page=2/user/ebooks/profile
典型场景分页、排序、筛选、搜索关键词用户ID、文章slug、产品ID

选型心法: 当你需要传递的参数是用来描述“怎么看”当前页面(过滤、排序、分页)时,用query。例如:/article/list?page=2&sort=views。 当你需要传递的参数是用来定义“看什么”页面(资源的唯一标识、视图的类型)时,用params。例如:/article/42(查看ID为42的文章) 或/article/42/edit(编辑ID为42的文章)。

一个综合案例: 假设有一个博客系统,URL 设计为:/blog/:category?tag=vue&sort=newest

  • :category(params):定义了核心资源集合,如“前端”、“后端”。它是路径的一部分。
  • tagsort(query):定义了在这个集合内的过滤和排序方式。它们是可选的查询参数。

在跳转时,你会这样写:

router.push({ name: 'BlogCategory', // 使用命名路由 params: { category: 'frontend' }, query: { tag: 'vue', sort: 'newest' } });

5. 实战避坑:参数丢失、类型与响应式问题

理论清晰了,但在实际编码中,仍有几个高频坑点需要特别注意。

5.1 Params 在刷新或直接输入 URL 时的丢失问题

虽然params是 URL 的一部分,但如果你在组件内直接通过router.push跳转,然后刷新页面,参数仍在。但有一种情况:当用户手动在浏览器地址栏输入一个带 params 的 URL 并回车时

问题在于,你的路由组件(例如UserProfile.vue)可能在setupcreated钩子中,直接从route.params.userId取值去调用 API。如果应用是从这个 URL 直接冷启动的,route对象是响应式的,取值没问题。但你需要确保你的数据获取逻辑能响应route.params的变化。

解决方案:使用watch监听路由参数

import { useRoute } from 'vue-router'; import { ref, watch } from 'vue'; const route = useRoute(); const userData = ref(null); // 定义一个获取用户数据的函数 const fetchUserData = (userId) => { // 调用 API... }; // 立即执行一次,并在参数变化时重新执行 watch( () => route.params.userId, // 监听 userId 参数的变化 (newUserId) => { if (newUserId) { fetchUserData(newUserId); } }, { immediate: true } // 组件创建时立即执行一次 );

这样,无论是通过导航跳转过来,还是直接输入 URL 刷新,数据都能正确获取。

5.2 参数类型的自动转换

如前所述,route.queryroute.params中的所有值都是字符串。这是 URL 本身的特性决定的。一个常见的错误是直接将其用于需要数字类型的逻辑中。

// 错误示例 const page = route.query.page; // 假设 URL 是 ?page=2 const total = 100; const maxPage = Math.ceil(total / 10); // 假设每页10条 if (page > maxPage) { // 这里在进行字符串和数字比较!'2' > 10 ?结果是 false,但逻辑可能错乱 // ... } // 正确做法:显式转换 const page = Number(route.query.page) || 1; // 转换失败或不存在时,默认为第1页 const id = parseInt(route.params.id, 10); const isActive = route.query.active === 'true'; // 布尔值转换

5.3 响应式依赖与逻辑分离

在组合式 API 中,route对象是响应式的。这意味着如果你在computedwatch中依赖了route的属性,当路由变化(如从/user/1跳到/user/2)时,相关逻辑会自动重新执行。这是一个非常强大的特性。

但要注意,如果你在模板中直接大量使用{{ route.query.xxx }}{{ route.params.xxx }},虽然方便,但可能会让模板变得难以阅读,并且逻辑分散。更好的做法是将依赖路由的参数提取到组件的响应式状态或计算属性中。

<template> <!-- 直接使用(可行但不推荐复杂逻辑) --> <div>当前搜索: {{ route.query.keyword }}</div> <!-- 推荐:使用计算属性 --> <div>当前搜索: {{ searchKeyword }}</div> <div>用户ID (数字): {{ userId }}</div> </template> <script setup> import { useRoute } from 'vue-router'; import { computed } from 'vue'; const route = useRoute(); // 将路由参数转化为具有明确语义和类型的计算属性 const searchKeyword = computed(() => route.query.keyword || ''); const userId = computed(() => Number(route.params.id) || null); // 更复杂的逻辑也可以封装在这里 const paginationInfo = computed(() => ({ page: Number(route.query.page) || 1, size: Number(route.query.size) || 20, total: 0 // 可以从API响应中更新 })); </script>

这样做的好处是:逻辑集中、类型安全、易于测试。你的组件模板只关心展示数据,而数据是如何从路由中转换而来的,则被清晰地封装在计算属性中。

6. 进阶:组合式函数封装与最佳实践

在大型项目中,为了避免在每个组件中重复处理参数转换、监听路由变化等逻辑,我们可以利用 Vue 3 的组合式函数进行封装,创建更清晰、可复用的代码。

6.1 封装一个安全的参数提取 Hook

我们可以创建一个useRouteParams钩子,来安全地提取并转换路由参数。

// hooks/useRouteParams.js import { useRoute } from 'vue-router'; import { computed } from 'vue'; /** * 安全地获取并转换路由参数 * @param {Object} config 配置对象 * @param {Object} config.params 参数定义,键为参数名,值为转换函数或默认值 * @returns {Object} 转换后的参数对象 */ export function useRouteParams(config = {}) { const route = useRoute(); const { params: paramDefs = {}, query: queryDefs = {} } = config; const parsedParams = {}; const parsedQuery = {}; // 处理 params Object.keys(paramDefs).forEach(key => { const def = paramDefs[key]; const rawValue = route.params[key]; parsedParams[key] = typeof def === 'function' ? def(rawValue) : (rawValue !== undefined ? rawValue : def); }); // 处理 query Object.keys(queryDefs).forEach(key => { const def = queryDefs[key]; const rawValue = route.query[key]; parsedQuery[key] = typeof def === 'function' ? def(rawValue) : (rawValue !== undefined ? rawValue : def); }); return { params: parsedParams, query: parsedQuery, // 也可以返回原始的 route 对象以备不时之需 route }; }

在组件中使用

<script setup> import { useRouteParams } from '@/hooks/useRouteParams'; // 定义参数的类型转换规则和默认值 const { params, query } = useRouteParams({ params: { userId: value => parseInt(value, 10) || null, // 将 userId 转换为数字,无效则为null }, query: { page: value => Math.max(1, parseInt(value, 10) || 1), // 页码至少为1 keyword: value => value || '', // 关键词,默认为空字符串 active: value => value === 'true', // 将字符串 'true' 转换为布尔值 } }); // 现在可以直接使用转换后的、类型安全的参数 console.log(params.userId); // number | null console.log(query.page); // number (至少为1) console.log(query.active); // boolean </script>

6.2 封装一个新窗口导航的 Hook

同样,我们可以将打开新窗口的复杂逻辑封装起来。

// hooks/useNewWindowNavigation.js import { useRouter } from 'vue-router'; export function useNewWindowNavigation() { const router = useRouter(); /** * 在新窗口打开路由 * @param {RouteLocationRaw} to - 路由目标,同 router.push 参数 * @param {string} windowFeatures - window.open 的窗口特性字符串 * @returns {Window|null} 打开的新窗口对象,如果被浏览器阻止则为 null */ const openInNewWindow = (to, windowFeatures = '') => { // 解析路由,获取标准 URL const resolved = router.resolve(to); // 尝试打开新窗口 const newWin = window.open('', '_blank', windowFeatures); if (newWin) { // 将解析后的 URL 赋给新窗口 newWin.location.href = resolved.href; return newWin; } else { // 处理被浏览器拦截的情况(例如非用户手势触发) console.warn('新窗口可能被浏览器阻止。请确保此操作由用户点击事件触发。'); // 可选降级方案:在当前窗口打开? // router.push(to); return null; } }; /** * 打开新窗口并建立简单的消息通信 * @param {RouteLocationRaw} to - 路由目标 * @param {Object} options - 选项 * @param {Function} options.onMessage - 接收消息的回调 * @param {*} options.initialData - 要传递的初始数据 * @returns {Object} 包含新窗口引用和清理函数 */ const openWithCommunication = (to, options = {}) => { const newWin = openInNewWindow(to, options.windowFeatures); if (!newWin) { return { window: null, cleanup: () => {} }; } const { onMessage, initialData } = options; let messageHandler = null; // 设置消息监听 if (onMessage && typeof onMessage === 'function') { messageHandler = (event) => { // 重要:验证消息来源,确保安全 if (event.origin === window.location.origin) { onMessage(event.data, event); } }; window.addEventListener('message', messageHandler); } // 发送初始数据(需要新窗口配合监听 load 事件) if (initialData !== undefined) { // 延迟发送,确保新窗口已加载并准备好接收消息 const sendInitialData = () => { newWin.postMessage( { type: 'INIT_DATA', payload: initialData }, window.location.origin ); }; // 简单延迟,实际项目中可能需要更可靠的机制 setTimeout(sendInitialData, 500); } // 返回清理函数,用于组件卸载时移除监听器 const cleanup = () => { if (messageHandler) { window.removeEventListener('message', messageHandler); } }; return { window: newWin, cleanup }; }; return { openInNewWindow, openWithCommunication }; }

在组件中使用

<script setup> import { useNewWindowNavigation } from '@/hooks/useNewWindowNavigation'; import { onUnmounted } from 'vue'; const { openWithCommunication } = useNewWindowNavigation(); const handleOpenHelp = () => { const { window: helpWindow, cleanup } = openWithCommunication( { name: 'HelpDocument', query: { topic: 'form-validation' } }, { onMessage(data) { if (data === 'HELP_CLOSED') { console.log('帮助窗口已关闭,可以更新UI状态'); // 例如,隐藏一个提示角标 } }, initialData: { highlightSection: 'requiredFields' } } ); // 组件卸载时清理消息监听 onUnmounted(() => { cleanup(); }); }; </script>

通过这样的封装,我们将路由参数处理和新窗口导航的复杂性从业务组件中剥离出来,使组件代码更专注于业务逻辑本身,显著提升了代码的可维护性和可测试性。这些模式在实际的中大型 Vue 3 项目中非常值得采用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/14 5:40:24

厦门市住房和建设局网站怎么进?老厦门人教你快速查房源、查政策,避免踩雷

各位在厦门打拼的朋友们,大家好。我是你们的老朋友,一个在这座城市摸爬滚打了十年的“新房客”。今天不想聊那些高大上的宏观经济数据,也不想讲什么虚无缥缈的买房哲学,咱们来聊点最实在、最接地气,甚至有点“枯燥”但能帮你省下大把时间和金钱的事——如何正确使用厦门市…

作者头像 李华
网站建设 2026/8/14 5:40:11

揭秘泉州seo网站建设费用背后的真相与省钱避坑指南

在这个数字化浪潮汹涌的时代,对于泉州的中小企业老板和创业者来说,拥有一个高质量的官网早已不是“可选项”,而是“必选项”。大家每天在网上刷手机、看新闻,谁家网站做得漂亮、加载快、搜索引擎好找,谁就能第一时间赢得客户的信任。但是,当大家真正想要着手做网站,或者…

作者头像 李华
网站建设 2026/8/14 5:39:28

CTF-NetA v2.5新特性:WINRM流量解密与4G/5G异常流量识别

CTF-NetA v2.5新特性&#xff1a;WINRM流量解密与4G/5G异常流量识别 【免费下载链接】CTF-NetA CTF-NetA是一款专门针对CTF比赛的网络流量分析工具&#xff0c;可以对常见的网络流量进行分析&#xff0c;快速自动获取flag。 项目地址: https://gitcode.com/gh_mirrors/ct/CTF…

作者头像 李华
网站建设 2026/8/14 5:39:13

2024年揭秘江北网站建设价格:为什么你的预算总不够用?资深开发者教你避坑

在这个数字化浪潮席卷每一个角落的今天,如果你还在问“我的生意需不需要做一个网站”,那答案可能有点扎心:你需要。尤其是对于身处江北这片繁华商业沃土上的企业来说,一个专业的官网不仅仅是个展示窗口,它是你24小时在线的销售代表,是你品牌背书的金字招牌。但是,当你真…

作者头像 李华
网站建设 2026/8/14 5:38:14

解锁更简单的接口费用计算方式,让您的业务更自由!

小于科技 在当今市场竞争激烈的环境中&#xff0c;企业对接接口成为推动业务发展的关键一环。然而&#xff0c;接口费用的计算方式却让许多企业感到困扰。不同的计费方式可能限制了业务场景的灵活性&#xff0c;而我们的公司将为您提供最简洁、最便捷的接口费用计算方式&#…

作者头像 李华
网站建设 2026/8/14 5:37:57

上海网站建设市场分析:2024年中小企业如何通过专业定制打破流量困局与品牌僵局

最近一直在和几个朋友喝茶聊天,话题绕不开一个老生常谈却又让人头疼的问题:现在的企业官网到底还有没有用?特别是在上海这个竞争激烈、节奏飞快的魔都,很多老板花了几万甚至十几万做的那个网站,最后往往变成了只能用来“放地址”的电子名片,偶尔还能顺便展示一下公司的资…

作者头像 李华