摘要

改变 router-link-active 的应用方式,使之与 router 的概念更加一致。现在,如果生成的 url 是当前位置的部分版本,那么链接就是活跃的。这产生了一些问题:

  • 别名不(一定)匹配
  • 当在一个带 / 的子节点上时,到其父节点的链接不会显示为 active 的,因为 url 是不同的
  • query 被用来使链接 active

基本范例

N/A

动机

上面提到的问题很难或不可能在用户空间中实现,而实现当前的行为则非常简单明了:

  1. <router-link v-slot="{ route }">
  2. partial path: {{ $route.path.startsWith(route.path) }}
  3. <br />
  4. partial path + partial query: {{ $route.path.startsWith(route.path) && includesQuery($route.query, route.query) }}
  5. </router-link>
  1. // this is only necessary if we care about matching the query
  2. function includesQuery(outter, inner) {
  3. for (let key in inner) {
  4. let innerValue = inner[key]
  5. let outterValue = outter[key]
  6. if (typeof innerValue === 'string') {
  7. if (innerValue !== outterValue) return false
  8. } else {
  9. if (
  10. !Array.isArray(outterValue) ||
  11. outterValue.length !== innerValue.length ||
  12. innerValue.some((value, i) => value !== outterValue[i])
  13. )
  14. return false
  15. }
  16. }
  17. return true
  18. }

从 router 的角度来看,它们也更有意义,因为链接将基于路由记录是否激活。而对嵌套的路由和别名特别重要,如果它们不与 router-link 的 location 共享当前 url 的一部分,那么他们目前就不会被激活。从 navigation 的角度看,active 的链接不触发新的 navigation 也更有意义(除了在嵌套路由的情况下,它可能是一个 navigation)。

具体设计

active 行为应该与 router 的 Matcher 部分有更多的联系,这意味着它应该与当前 URL 所呈现的内容有关,这也是 Router Record 的一部分。因此,queryhash 不应该影响这个。这似乎也是人们真正感兴趣的东西但是我不知道人们是否真的主动从 active 的 **query** 受益。

router-link-exact-active

这个变化也影响了 exact active 的行为。从 Matcher 的角度看,只有当当前的 router 与来自链接的 router 完全相同时,才会应用 router-link-exact-active 这个类。这意味着 queryhash 时不相关的(与 active 相同)。

这种新的 active 行为完全消除了 root link(/)的 exact prop 的注意事项,并使用 https://github.com/vuejs/rfcs/pull/36,而不会引入不一致的情况。

嵌套的 Routes

值得注意的是,只有当与渲染的路由相关的参数相同时,嵌套的路由才会匹配。

例如,给定这些路由:

  1. const routes = [
  2. {
  3. path: '/parent/:id',
  4. children: [
  5. // empty child
  6. { path: '' },
  7. // child with id
  8. { path: 'child/:id' },
  9. { path: 'child-second/:id' }
  10. ]
  11. }
  12. ]

如果当前的 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 不再活跃:

例如,给定这些路由:

  1. const routes = [
  2. { path: '/movies' },
  3. { path: '/movies/new' },
  4. { path: '/movies/search' }
  5. ]

如果当前路由是 /movies/new,这些链接将被激活:

url active exact active
/movies
/movies/new
/movies/search

注意:这种行为与实际行为不同。

值得注意的是,有可能将它们嵌套在一起,以便仍然从 active 的链接中获益:

  1. // Vue 3
  2. import { h } from 'vue'
  3. import { RouterView } from 'vue-router'
  4. const routes = [
  5. {
  6. path: '/movies',
  7. // we need this to render the children (see note below)
  8. component: { render: () => h(RouterView) },
  9. // for vue 2 use render: h => h('RouterView')
  10. children: [
  11. { path: 'new' },
  12. // different child
  13. { path: 'search' }
  14. ]
  15. }
  16. ]

如果当前的 route 是 /movies/new,这些链接将被激活:

url active exact active
/movies
/movies/new
/movies/search

注意:为了使其更容易使用,我们也许可以允许 component 不存在,并在内部表现的像有一个 component 选项可以渲染一个 RouterView 组件一样。

别名

鉴于一个别名只是一个不用的 path,而把其他的东西都保留在一个记录上,所以当它们别名代表的 path 被匹配时,别名就会被激活,反之亦然。

例如,给定这些路由:

  1. const routes = [{ path: '/movies', alias: ['/films'] }]

如果当前的 route 是 /movies/films,这些链接将被激活:

url active exact active
/movies
/films

嵌套别名

在处理嵌套路由的嵌套 children 时,行为是类似的:

例如,给定这些路由:

  1. const routes = [
  2. {
  3. path: '/parent/:id',
  4. alias: '/p/:id',
  5. children: [
  6. // empty child
  7. { path: '' },
  8. // child with id
  9. { path: 'child/:id', alias: 'c/:id' }
  10. ]
  11. }
  12. ]

如果当前的 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,让它以 / 开头,在这种情况下,同样的规则也适用。

例如,给定这些路由:

  1. const routes = [
  2. {
  3. path: '/parent/:id',
  4. alias: '/p/:id',
  5. name: 'parent',
  6. children: [
  7. // empty child
  8. { path: '', alias: ['alias', '/p_:id'], name: 'child' },
  9. // child with absolute path. we need to add an `id` because the parent needs it
  10. { path: '/p_:id/absolute-a', alias: 'as-absolute-a' },
  11. // same as above but the alias is absolute
  12. { path: 'as-absolute-b', alias: '/p_:id/absolute-b' }
  13. ]
  14. }
  15. ]

如果当前的 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 来工作。它的主要目的是绕过 / 的警告,但它也检查 queryhash。正是因为如此,有了新的 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 行为,对 queryhash 进行全面匹配,而不是仅仅依赖参数。
  • 改变 active 行为,只适用于 router 的 path 部分(这包括参数),忽略 queryhash

采纳策略

  • 增加 exact-path,以缓解 v3 中现有的问题。

没有解决的问题

N/A