链接
概述
SLink 是一个多态链接组件,支持内部路由(通过 RouterLink)和外部链接(通过 <a> 标签)。自动检测外部链接并设置安全的 target 和 rel 属性,支持禁用状态和多态渲染(as/asChild)。
特性
- 🔗 智能路由:自动区分内部路由(
to)与外部链接(href),无 RouterLink 环境下优雅降级为<a>。 - 🧩 可插拔链接组件:通过
SConfigProvider的linkComponent注入框架专属组件(如 NuxtNuxtLink)。 - 🛡️ 安全默认值:外部链接自动设置
target="_blank"和rel="noopener noreferrer",防止反向链接攻击。 - 🔧 多态渲染:通过
as/asChild支持渲染为任意元素或组件。 - ♿ 无障碍禁用:禁用状态设置
aria-disabled、role="link"、tabindex="-1",并阻止点击事件。 - 🎨 样式定制:通过
classprop 覆盖样式,linkVariants提供基础工具类。
用法
演示
基础
无障碍
- 禁用状态:设置
aria-disabled="true"、role="link"、tabindex="-1",并通过preventDefault()阻止点击导航。视觉上通过data-disabled属性触发cursor-not-allowed和opacity-50。 - 外部链接安全:自动添加
rel="noopener noreferrer",防止新打开的页面访问window.opener。
API
Aria 层
3Link
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
class | ClassValue | — | 应用于根元素的额外类名。 |
to | string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric | — | Route Location:点击该链接后应跳转到的位置。 |
replace | boolean | — | 调用 `router.replace` 而不是 `router.push`。 |
href | string | — | 点击链接时应跳转到的 URL。 |
disabled | boolean | — | 当值为 `true` 时,该链接将被禁用。 |
activeClass | string | — | 当链接处于活动状态时应用的类 |
exactActiveClass | string | — | 当链接完全有效时应用的类 |
inactiveClass | string | — | 链接处于非活动状态时应应用的类。 |
prefetchedClass | string | — | 一个用于标记已预取链接的类。 |
external | boolean | — | 强制将链接视为外部链接(true)或内部链接(false)。这有助于处理边界情况 |
ariaCurrentValue | 'true' | 'false' | 'page' | 'date' | 'time' | 'step' | 'location' | `'page'` | 当链接处于活动状态时,传递给 `aria-current` 属性的值。 |
viewTransition | boolean | — | 如果支持,请将 `router.push()` 返回的 Promise 传递给 `document.startViewTransition()`。 |
target | (string & {}) | '_blank' | '_parent' | '_self' | '_top' | null | — | 将链接的 URL 作为浏览上下文的名称显示在何处。 |
rel | (string & {}) | 'noopener' | 'noreferrer' | 'nofollow' | 'sponsored' | 'ugc' | null | 'noopener noreferrer' | 应用于链接的 rel 属性值。对于外部链接,默认值为“noopener noreferrer”。 |
noRel | boolean | — | 如果设置为 true,则不会在链接中添加 rel 属性 |
prefetch | boolean | — | 启用后,将预取视口内链接的中间件、布局和有效载荷。 |
prefetchOn | 'visibility' | 'interaction' | Partial<{ visibility: boolean; interaction: boolean; }> | — | 允许控制何时预取链接。默认情况下,仅在可见性发生变化时才会触发预取。 |
noPrefetch | boolean | — | 用于禁用 `prefetch` 属性的逃逸机制。 |
trailingSlash | 'append' | 'remove' | — | 一个用于在此特定链接的 `href` 中添加或移除尾部斜杠的选项。
如果提供了此选项,将覆盖全局 `trailingSlash` 选项。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
LinkBase
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
href | string | — | 点击链接时应跳转到的 URL。 |
to | string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric | — | Route Location:点击该链接后应跳转到的位置。 |
external | boolean | — | 强制将链接视为外部链接(true)或内部链接(false)。这有助于处理边界情况 |
target | (string & {}) | '_blank' | '_parent' | '_self' | '_top' | null | — | 将链接的 URL 作为浏览上下文的名称显示在何处。 |
LinkExtra
属性
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
replace | boolean | — | 调用 `router.replace` 而不是 `router.push`。 |
disabled | boolean | — | 当值为 `true` 时,该链接将被禁用。 |
asChild | boolean | — | 将默认渲染元素替换为作为子元素传递的元素,并合并两者的属性及行为。 |
as | AsTag | Component | 'div' | 该组件应渲染为的元素或组件。可通过 `asChild` 进行覆盖。 |
activeClass | string | — | 当链接处于活动状态时应用的类 |
exactActiveClass | string | — | 当链接完全有效时应用的类 |
inactiveClass | string | — | 链接处于非活动状态时应应用的类。 |
prefetchedClass | string | — | 一个用于标记已预取链接的类。 |
ariaCurrentValue | 'true' | 'false' | 'page' | 'date' | 'time' | 'step' | 'location' | `'page'` | 当链接处于活动状态时,传递给 `aria-current` 属性的值。 |
viewTransition | boolean | — | 如果支持,请将 `router.push()` 返回的 Promise 传递给 `document.startViewTransition()`。 |
rel | (string & {}) | 'noopener' | 'noreferrer' | 'nofollow' | 'sponsored' | 'ugc' | null | 'noopener noreferrer' | 应用于链接的 rel 属性值。对于外部链接,默认值为“noopener noreferrer”。 |
noRel | boolean | — | 如果设置为 true,则不会在链接中添加 rel 属性 |
prefetch | boolean | — | 启用后,将预取视口内链接的中间件、布局和有效载荷。 |
prefetchOn | 'visibility' | 'interaction' | Partial<{ visibility: boolean; interaction: boolean; }> | — | 允许控制何时预取链接。默认情况下,仅在可见性发生变化时才会触发预取。 |
noPrefetch | boolean | — | 用于禁用 `prefetch` 属性的逃逸机制。 |
trailingSlash | 'append' | 'remove' | — | 一个用于在此特定链接的 `href` 中添加或移除尾部斜杠的选项。
如果提供了此选项,将覆盖全局 `trailingSlash` 选项。 |
说明
架构与基准差异
VeanUI 将链接拆分为 Aria 层(路由检测、禁用处理、RouterLink 集成)和 styled 层(linkVariants 样式)。这不同于 Nuxt Link、React Router Link 等单包方案。
| 方面 | VeanUI | Nuxt Link / React Router Link / Next.js Link |
|---|---|---|
| 架构 | Aria + styled 分层 | 单包 |
| 路由检测 | 自动检测 http 前缀、external prop、RouterLink 可用性 | 显式 <NuxtLink> / <Link> |
| 外部链接 | 自动 target="_blank" + rel="noopener noreferrer" | 需手动设置 |
| 禁用状态 | aria-disabled + tabindex="-1" + preventDefault | 需手动处理 |
| 多态 | as/asChild via Primitive | as / passHref |
| 样式 | UnoCSS 工具类 + linkVariants | CSS Modules / styled / 无 |
FAQ
何时用 to vs href? 使用 to 进行内部路由导航(依赖 RouterLink),使用 href 进行外部链接跳转。当 to 为 http 开头的字符串时,组件自动视为外部链接并渲染 <a> 标签。
没有安装 vue-router 时能用吗? 可以。组件通过 resolveComponent('RouterLink') 检测 RouterLink 是否可用。不可用时自动降级为 <a> 标签渲染。
可以对接框架专属的链接组件吗(如 Nuxt NuxtLink、带语言前缀的 Link)? 可以。在 SConfigProvider 上设置 linkComponent(与 iconRender 相同的注入机制),Link 的内部链接将改用该组件渲染,替代全局 RouterLink。LinkProps 未声明的框架专属 props(如 locale)会以 attrs 透传给注入组件,可通过模块增强 LinkProps 获得类型提示。外部链接仍渲染原生 <a>。
如何禁用链接? 设置 disabled: true。组件会设置 aria-disabled="true"、tabindex="-1"、role="link",并通过 preventDefault() 阻止点击导航。视觉上添加 data-disabled 属性触发 cursor-not-allowed 和 opacity-50。
as 和 asChild 有什么区别?as 指定根元素标签(如 as="button" 渲染为 <button>)。asChild 让组件不渲染自身根元素,而是将属性和事件透传给子元素(类似 Radix UI 的 asChild 模式)。
target 和 rel 的默认值是什么? 外部链接默认 target="_blank"、rel="noopener noreferrer";内部路由默认 target="_self"。可通过 props 显式覆盖。