- 开始时间:2020-02-28
- 目标主要版本:Router v4
- 引用 issue:https://github.com/vuejs/vue-router/issues/2040
- 实现的 PR:N/A
摘要
改变 router-link-active 的应用方式,使之与 router 的概念更加一致。现在,如果生成的 url 是当前位置的部分版本,那么链接就是活跃的。这产生了一些问题:
- 别名不(一定)匹配
- 当在一个带 / 的子节点上时,到其父节点的链接不会显示为 active 的,因为 url 是不同的
query被用来使链接 active
基本范例
N/A
动机
上面提到的问题很难或不可能在用户空间中实现,而实现当前的行为则非常简单明了:
<router-link v-slot="{ route }">partial path: {{ $route.path.startsWith(route.path) }}<br />partial path + partial query: {{ $route.path.startsWith(route.path) && includesQuery($route.query, route.query) }}</router-link>
// this is only necessary if we care about matching the queryfunction includesQuery(outter, inner) {for (let key in inner) {let innerValue = inner[key]let outterValue = outter[key]if (typeof innerValue === 'string') {if (innerValue !== outterValue) return false} else {if (!Array.isArray(outterValue) ||outterValue.length !== innerValue.length ||innerValue.some((value, i) => value !== outterValue[i]))return false}}return true}
从 router 的角度来看,它们也更有意义,因为链接将基于路由记录是否激活。而对嵌套的路由和别名特别重要,如果它们不与 router-link 的 location 共享当前 url 的一部分,那么他们目前就不会被激活。从 navigation 的角度看,active 的链接不触发新的 navigation 也更有意义(除了在嵌套路由的情况下,它可能是一个 navigation)。
具体设计
active 行为应该与 router 的 Matcher 部分有更多的联系,这意味着它应该与当前 URL 所呈现的内容有关,这也是 Router Record 的一部分。因此,query 和 hash 不应该影响这个。这似乎也是人们真正感兴趣的东西,但是我不知道人们是否真的主动从 active 的 **query** 受益。
router-link-exact-active
这个变化也影响了 exact active 的行为。从 Matcher 的角度看,只有当当前的 router 与来自链接的 router 完全相同时,才会应用 router-link-exact-active 这个类。这意味着 query 和 hash 时不相关的(与 active 相同)。
这种新的 active 行为完全消除了 root link(/)的 exact prop 的注意事项,并使用 https://github.com/vuejs/rfcs/pull/36,而不会引入不一致的情况。
嵌套的 Routes
值得注意的是,只有当与渲染的路由相关的参数相同时,嵌套的路由才会匹配。
例如,给定这些路由:
const routes = [{path: '/parent/:id',children: [// empty child{ path: '' },// child with id{ path: 'child/:id' },{ path: 'child-second/:id' }]}]
如果当前的 route 是 /parent/1/child/2,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /parent/1/child/2 | ✅ | ✅ |
| /parent/1/child/3 | ❌ | ❌ |
| /parent/1/child-second/2 | ❌ | ❌ |
| /parent/1 | ✅ | ❌ |
| /parent/2 | ❌ | ❌ |
| /parent/2/child/2 | ❌ | ❌ |
| /parent/2/child-second/2 | ❌ | ❌ |
不相关但是相似的 routes
从纪录角度看不相关单有共同路径的 path 不再活跃:
例如,给定这些路由:
const routes = [{ path: '/movies' },{ path: '/movies/new' },{ path: '/movies/search' }]
如果当前路由是 /movies/new,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /movies | ❌ | ❌ |
| /movies/new | ✅ | ✅ |
| /movies/search | ❌ | ❌ |
注意:这种行为与实际行为不同。
值得注意的是,有可能将它们嵌套在一起,以便仍然从 active 的链接中获益:
// Vue 3import { h } from 'vue'import { RouterView } from 'vue-router'const routes = [{path: '/movies',// we need this to render the children (see note below)component: { render: () => h(RouterView) },// for vue 2 use render: h => h('RouterView')children: [{ path: 'new' },// different child{ path: 'search' }]}]
如果当前的 route 是 /movies/new,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /movies | ✅ | ❌ |
| /movies/new | ✅ | ✅ |
| /movies/search | ❌ | ❌ |
注意:为了使其更容易使用,我们也许可以允许 component 不存在,并在内部表现的像有一个 component 选项可以渲染一个 RouterView 组件一样。
别名
鉴于一个别名只是一个不用的 path,而把其他的东西都保留在一个记录上,所以当它们别名代表的 path 被匹配时,别名就会被激活,反之亦然。
例如,给定这些路由:
const routes = [{ path: '/movies', alias: ['/films'] }]
如果当前的 route 是 /movies 或 /films,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /movies | ✅ | ✅ |
| /films | ✅ |
嵌套别名
在处理嵌套路由的嵌套 children 时,行为是类似的:
例如,给定这些路由:
const routes = [{path: '/parent/:id',alias: '/p/:id',children: [// empty child{ path: '' },// child with id{ path: 'child/:id', alias: 'c/:id' }]}]
如果当前的 route 是 /parent/1/child/2、/p/1/child/2、/p/1/c/2 或 /parent/1/c/2,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /parent/1/child/2 | ✅ | ✅ |
| /parent/1/c/2 | ✅ | ✅ |
| /p/1/child/2 | ✅ | ✅ |
| /p/1/c/2 | ✅ | ✅ |
| /p/1/child/3 | ❌ | ❌ |
| /parent/1/child/3 | ❌ | ❌ |
| /parent/1 | ✅ | ❌ |
| /p/1 | ✅ | ❌ |
| /parent/2 | ❌ | ❌ |
| /p/2 | ❌ | ❌ |
绝对嵌套别名(Absolute nested aliases)
嵌套的 children 可以有一个绝对 path,让它以 / 开头,在这种情况下,同样的规则也适用。
例如,给定这些路由:
const routes = [{path: '/parent/:id',alias: '/p/:id',name: 'parent',children: [// empty child{ path: '', alias: ['alias', '/p_:id'], name: 'child' },// child with absolute path. we need to add an `id` because the parent needs it{ path: '/p_:id/absolute-a', alias: 'as-absolute-a' },// same as above but the alias is absolute{ path: 'as-absolute-b', alias: '/p_:id/absolute-b' }]}]
如果当前的 route 是 /p_1/absolute-a、/p/1/as-absolute-a 或 /parent/1/as-absolute-a,这些链接将被激活:
| url | active | exact active |
|---|---|---|
| /p/1/as-absolute-a | ✅ | ✅ |
| /p_1/absolute-a | ✅ | ✅ |
| /parent/1/absolute-a | ✅ | ✅ |
| /parent/2/absolute-a | ❌ | ❌ |
| /parent/1/absolute-b | ❌ | ❌ |
| /p/1/absolute-b | ❌ | ❌ |
| /p_1/absolute-b | ❌ | ❌ |
| /parent/1 | ✅ | ❌ |
| /p/1 | ✅ | ❌ |
| /parent/1/alias | ✅ | ❌ |
| /p/1/alias | ✅ | ❌ |
| /p_1 | ✅ | ❌ |
| /parent/2 | ❌ | ❌ |
| /p/2 | ❌ | ❌ |
请注意,空的 path 记录是活动的,但与其他子记录 /p/1/absolute-b 的并不完全相同。它所有的别名也是活动的,因为它们是一个空 path 的别名。如果情况相反:path 不是空的,但其中一个别名是空的 path,那么它们都不会是活动的,因为原始 path 优先于别名。
具名的嵌套路由
如果 url 是通过父级的 name 来解决的, 在这种情况下,它将不包括空 path 的子路由。这一点非常重要,因为它们都解析到相同的 url,但当用于 router-link 的 prop 时,当涉及到 active 和/或 exact active 时,它们会产生不同的结果。这与它们所渲染的不同并且与其余的 active 行为是一致的。
例如,给前面例子中的 routes,如果当前的 location 是 /parent/1,并且,父子视图都在渲染,这意味着我们实际上是在 {name: 'child'} 而不是在 {name: 'parent'},这里面有一个与前面类似的表格,但也包括 to:
**to**‘s value |
resolved url | active | exact active |
|---|---|---|---|
{ name: 'parent', params: { id: '1' } } |
/parent/1 (parent) |
✅ | ❌ |
'/parent/1' |
/parent/1 (child) |
✅ | ✅ |
{ name: 'child', params: { id: '1' } } |
/parent/1 (child) |
✅ | ✅ |
'/p_1' |
/p_1 (child) |
✅ | ✅ |
'/parent/1/alias' |
/parent/1/alias (child) |
✅ | ✅ |
但是,如果当前的 location 是 {name: 'parent'},它仍然会产生相同的 url /parent/1,但是一个不同的表:
**to**‘s value |
resolved url | active | exact active |
|---|---|---|---|
{ name: 'parent', params: { id: '1' } } |
/parent/1 (parent) |
✅ | ✅ |
'/parent/1' |
/parent/1 (child) |
❌ | ❌ |
{ name: 'child', params: { id: '1' } } |
/parent/1 (child) |
❌ | ❌ |
'/p_1' |
/p_1 (child) |
❌ | ❌ |
'/parent/1/alias' |
/parent/1/alias (child) |
❌ | ❌ |
重复的参数
用重复的参数,如:
/articles/:id+/articles/:id*
所有的参数都必须匹配,以相同的 exact 循序,使一个链接即是 active 又是 exact active 的。
exact 属性
在这些变化之前,exact 通过匹配整个 location 来工作。它的主要目的是绕过 / 的警告,但它也检查 query 和 hash。正是因为如此,有了新的 active 行为,exact 属性的唯一作用是,只有当 router-link- exact-active 也存在时,链接才会显示 router-link-active。但这其实已经没用什么用了,因为我们可以直接使用 router-link-exact-active 类来定位这个元素。正因为如此,我认为可以从 router-link 中删除 exact 属性。这样,https://github.com/vuejs/rfcs/pull/37,同时仍然为 location 的 path 部分引入 exact 行为,就像上面解释的那样。
一些用户可能不得不将 CSS 中使用的 class 从 router-link-exact-active 改为 router-link-active,以适应这一变化。
缺点
- 这并不向后兼容。也许值得在 Vue Router v3 中加入
exact-path。 - 使用
exact属性的用户不得不依赖router-link-exact-active或者使用exact-active-class属性。 - 函数
includesQuery必须由用户添加。
备选方案
- 当
router-link-exact-active也被应用时,留下来exact的属性应用router-link-active - 保持 active 行为,对
query和hash进行全面匹配,而不是仅仅依赖参数。 - 改变 active 行为,只适用于 router 的
path部分(这包括参数),忽略query和hash。
采纳策略
- 增加
exact-path,以缓解 v3 中现有的问题。
没有解决的问题
N/A
