当前位置:   article > 正文

vue router 参数_让构建单页面应用变得易如反掌 Vue3中的router

router.option.routes和router.getroutes()

2d60d03b6fdef4d82edd162ddb0d1007.gif

f54ee53978d623d7f18bc48730f6cf26.png

作者: Leiy

https://segmentfault.com/a/1190000022582928

前言:

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。

本文基于的源码版本是 vue-next-router alpha.10,为了与 Vue 2.0 中的 Vue Router 区分,下文将 vue-router v3.1.6 称为 vue2-router。

本文旨在帮助更多人对新版本 Router 有一个初步的了解,如果文中有误导大家的地方,欢迎留言指正。

重大改进

此次 Vue 的重大改进随之而来带来了 Vue Router 的一系列改进,现阶段(alpha.10)相比 vue2-router 的主要变化,总结如下:

61ce4eb3b942f8b103a20d9ecd7410b1.png

构建选项 mode

由原来的 mode: "history" 更改为 history: createWebHistory()。(设置其他 mode 也是同样的方式)。

// vue2-routerconst router = new VueRouter({  mode: 'history',  ...})// vue-next-routerimport { createRouter, createWebHistory } from 'vue-next-router'const router = createRouter({  history: createWebHistory(),  ...})

4d5252c34a9cd15938a5208d6d3f303e.png

构建选项 base

传给 createWebHistory()(和其他模式) 的第一个参数作为 base。

//vue2-routerconst router = new VueRouter({  base: __dirname,})// vue-next-routerimport { createRouter, createWebHistory } from 'vue-next-router'const router = createRouter({  history: createWebHistory('/'),  ...})

02b67e66ffa1e8d875a2a5bf9e4d910b.png

使用带有自定义正则表达式的参数

捕获所有路由 ( /* ) 时,现在必须使用带有自定义正则表达式的参数进行定义:/:catchAll(.*)。

传给 createWebHistory()(和其他模式) 的第一个参数作为 base。//vue2-routerconst router = new VueRouter({  base: __dirname,})// vue-next-routerimport { createRouter, createWebHistory } from 'vue-next-router'const router = createRouter({  history: createWebHistory('/'),  ...}

c102e3cb82988ba08be0c372e6de347d.png

match 与 resolve 合并

router.match 与 router.resolve 合并在一起为 router.resolve,但签名略有不同。

// vue2-router...resolve ( to: RawLocation, current?: Route, append?: boolean) {  ...  return {    location,    route,    href,    normalizedTo: location,    resolved: route  }}// vue-next-routerfunction resolve(    rawLocation: Readonly,    currentLocation?: Readonly  ): RouteLocation & { href: string } {  ...  let matchedRoute = matcher.resolve(matcherLocation, currentLocation)  ...  return {    fullPath,    hash,    query: normalizeQuery(rawLocation.query),    ...matchedRoute,    redirectedFrom: undefined,    href: routerHistory.base + fullPath,  }}

f04a9609384606b556d9664112f2c481.png

删除 router.getMatchedComponents

删除 router.getMatchedComponents,从router.currentRoute.value.matched 中获取。

router.getMatchedComponents 返回目标位置或是当前路由匹配的组件数组 (是数组的定义/构造类,不是实例)。通常在服务端渲染的数据预加载时使用.

[{  aliasOf: undefined  beforeEnter: undefined  children: []  components: {default: {…}, other: {…}}  instances: {default: null, other: Proxy}  leaveGuards: []  meta: {}  name: undefined  path: "/"  props: ƒ (to)  updateGuards: []}]

b3c8f3715d4b2a00d9ffdef3289df02a.png

等待 router 准备就绪

如果使用 ,则可能需要等待 router 准备就绪才能挂载应用程序。

app.use(router)// Note: on Server Side, you need to manually push the initial locationrouter.isReady().then(() => app.mount('#app'))

一般情况下,正常挂载也是可以使用  的,但是现在导航都是异步的,如果在路由初始化时有路由守卫,则在 resolve 之前会出现一个初始渲染的过渡,就像给  提供一个 appear 一样。

3222b5e89f7c143784199cd6718bdca0.png

服务端渲染 (SSR)

在服务端渲染 (SSR) 中,需要使用一个三目运算符手动传递合适的 mode。

let history = isServer ? createMemoryHistory() : createWebHistory()let router = createRouter({ routes, history })// on server onlyrouter.push(req.url) // request urlrouter.isReady().then(() => {  // resolve the request})

91d52bcdb03d0acf68b60cdeb1964c93.png

push & resolve 

push 或者 resolve 一个不存在的命名路由时,将会引发错误,而不是导航到根路由 "/" 并且不显示任何内容。

在 vue2-router 中,当 push 一个不存在的命名路由时,路由会导航到根路由 "/" 下,并且不会渲染任何内容。

const router = new VueRouter({  mode: 'history',  routes: [{ path: '/', name: 'foo', component: Foo }]}this.$router.push({name: 'baz'})浏览器控制台只会提示如下警告,并且 url 会跳转到根路由 / 下。在 vue-next-router 中,同样做法会引发错误。const router = createRouter({  history: routerHistory(),  routes: [{ path: '/', name: 'foo', component: Foo }]})...import { useRouter } from 'vue-next-router'...const router = userRouter()router.push({name: 'baz'})) // 这段代码会报错

Active-RFCS

以下内容的改进来自 active-rfcs(active 就是已经讨论通过并且正在实施的特性)。

0021-router-link-scoped-slot0022-router-merge-meta-routelocation0028-router-active-link0029-router-dynamic-routing0033-router-navigation-failures - 本文略

3b54f3c360e556688ba48fc317305faf.png

router-link-scoped-slot

由原来的 mode: "history" 更改为 history: createWebHistory()。(设置其他 mode 也是同样的方式)。

// vue2-routerconst router = new VueRouter({  mode: 'history',  ...})// vue-next-routerimport { createRouter, createWebHistory } from 'vue-next-router'const router = createRouter({  history: createWebHistory(),  ...})

在 vue2-router 中,想要将  渲染成某种标签,例如 ,需要这么做:

按钮!-- 渲染结果 -->按钮

bd7254d472c992bf8082fbc2e54fe9a5.png

router-active-link

这个 rfc 改进的缘由是 gayhub 上名为 zamakkat 的大哥提出来的,他的 issues 主要内容是,有一个嵌套组件,像这样:

Foo (links to /pages/foo)
|-- Bar (links to /pages/foo/bar)

需求:需要突出显示当前选中的页面(并且只能突出显示一项)。

如果用户打开 /pages/foo,则仅 Foo 高亮显示。

如果用户打开 /pages/foo/bar,则仅 Bar 应高亮显示。

但是,Bar 页面也有分页,选择第二页时,会导航到 /pages/foo/bar?

page=2。vue2-router 默认情况下,路由匹配规则是「包含匹配」。

也就是说,当前的路径是 /pages 开头的,那么  都会被设置 CSS 类名。

在这个示例中,如果使用「精确匹配模式」(exact: true),则精确匹配将匹配 /pages/foo/bar,不会匹配 /pages/foo/bar?page=2 因为它在比较中包括查询参数 ?

page=2,所以当选择第二页面时,Bar 就不高亮显示了。

所以无论是「精确匹配」还是「包含匹配」都不能满足此需求。

为了解决上述问题和其他边界情况,此次改进使得 router-link-active 应用方式更严谨,处理此问题的核心:

// 确认路由 isActive 的行为function includesParams(  outer: RouteLocation['params'],  inner: RouteLocation['params']): boolean {  for (let key in inner) {    let innerValue = inner[key]    let outerValue = outer[key]    if (typeof innerValue === 'string') {      if (innerValue !== outerValue) return false    } else {      if (        !Array.isArray(outerValue) ||        outerValue.length !== innerValue.length ||        innerValue.some((value, i) => value !== outerValue[i])      )        return false    }  }  return true}

详情请参见这个 rfc。

3a912ecb57b473895d03db4cc78b01af.png

router-merge-meta-routelocation

在 vue2-router中,在处理嵌套路由时,meta 仅包含匹配位置的 route meta 信息。 

看个栗子:

{  path: '/parent',  meta: { nested: true },  children: [    { path: 'foo', meta: { nested: true } },    { path: 'bar' }  ]}

在导航到 /parent/bar 时,只会显示当前路由对应的 meta 信息为 {},不会显示父级的 meta 信息。

meta: {}

所以在这种情况下,需要通过 to.matched.some() 检查 meta 字段是否存在,而进行下一步逻辑。

router.beforeEach((to, from, next) => {  if (to.matched.some(record => record.meta.nested))    next('/login')  else next()})

因此为了避免使用额外的 to.matched.some, 这个 rfc 提议,将父子路由中的 meta 进行第一层合并(同 Object.assing())。

如果再遇到上述嵌套路由时,将可以直接通过 to.meta 获取信息。

router.beforeEach((to, from, next) => {  if (to.meta.nested) next('/login')  else next()})

更多详细介绍请看这个 rfc。

router-dynamic-routing

这个 rfc 的主要内容是,允许给 Router 添加和删除(单个)路由规则。

router.addRoute(route: RouteRecord) - 添加路由规则

router.removeRoute(name: string | symbol) - 删除路由规则

router.hasRoute(name: string | symbol): boolean - 检查路由是否存在

router.getRoutes(): RouteRecord[] - 获取当前路由规则的列表

相比 vue2-router 删除了动态添加多个路由规则的 router.addRoutes API。

在 Vue 2.0 中,给路由动态添加多个路由规则时,需要这么做:

router.addRoutes( [   { path: '/d', component: Home },   { path: '/b', component: Home } ])

而在 Vue 3.0 中,需要使用 router.addRoute() 单个添加记录,并且还可以使用更丰富的 API:

router.addRoute({ path: '/new-route', name: 'NewRoute', component: NewRoute})// 给现有路由添加子路由router.addRoute('ParentRoute', { path: 'new-route', name: 'NewRoute', component: NewRoute})// 根据路由名称删除路由router.removeRoute('NewRoute')// 获得路由的所有记录const routeRecords \= router.getRoutes()

关于 RfCS 上提出的改进,这里就介绍这么多,想了解更多的话,请移步到 active-rfcs。

5732aebf6f084a5b3c4d1942c142438b.png

走 进 源 码

相比 vue2-router 的 ES6-class 的写法 vue-next-router 的 function-to-function 的编写更易读也更容易维护。

83954613d5aa31a7f1e3f313821f078b.png

Router 的 install

暴露的 Vue 组件解析入口相对来说更清晰,开发插件时定义的 install 也简化了许多。我们现看下 vue2-router 源码中 install 方法的定义:

import View from './components/view'import Link from './components/link'export let _Vueexport function install (Vue) {  // 当 install 方法被同一个插件多次调用,插件将只会被安装一次。  if (install.installed && _Vue === Vue) return  install.installed = true  _Vue = Vue  const isDef = v => v !== undefined  const registerInstance = (vm, callVal) => {    let i = vm.$options._parentVnode    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {      i(vm, callVal)    }  }  // 将 router 全局注册混入,影响注册之后所有创建的每个 Vue 实例  Vue.mixin({    beforeCreate () {      if (isDef(this.$options.router)) {        this._routerRoot = this        this._router = this.$options.router        this._router.init(this)        Vue.util.defineReactive(this, '_route', this._router.history.current)      } else {        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this      }      // 注册实例,将 this 传入      registerInstance(this, this)    },    destroyed () {      registerInstance(this)    }  })  // 将 $router 绑定的 vue 原型对象上  Object.defineProperty(Vue.prototype, '$router', {    get () { return this._routerRoot._router }  })  // 将 $route 手动绑定到 vue 原型对象上  Object.defineProperty(Vue.prototype, '$route', {    get () { return this._routerRoot._route }  })  // 注册全局组件 RouterView、RouterLink  Vue.component('RouterView', View)  Vue.component('RouterLink', Link)  const strats = Vue.config.optionMergeStrategies  // use the same hook merging strategy for route hooks  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}

我们可以看到,在 2.0 中,Router 提供的 install() 方法中更触碰底层,需要用到选项的私有方法 _parentVnode(),还会用的 Vue.mixin() 进行全局混入,之后会手动将 $router、$route 绑定到 Vue 的原型对象上。

VueRouter.install = installVueRouter.version = '__VERSION__'// 以 src 方法导入if (inBrowser && window.Vue) {  window.Vue.use(VueRouter)}

做了这么多事情之后,然后会在定义 VueRouter 类的文件中,将 install() 方法绑定到 VueRouter 的静态属性 install 上,以符合插件的标准。

安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。

我们可以看到,在 2.0 中开发一个插件需要做的事情很多,install 要处理很多事情,这对不了解 Vue 的童鞋,会变得很困难。

说了这么多,那么 vue-next-router 中暴露的 install 是什么样的呢?

applyRouterPlugin() 方法就是处理 install() 全部逻辑的地方,请看源码:

import { App, ComputedRef, reactive, computed } from 'vue'import { Router } from './router'import { RouterLink } from './RouterLink'import { RouterView } from './RouterView'export function applyRouterPlugin(app: App, router: Router) {  // 全局注册组件 RouterLink、RouterView  app.component('RouterLink', RouterLink)  app.component('RouterView', RouterView)  //省略部分代码  // 注入 Router 实例,源码其他地方会用到  app.provide(routerKey, router)  app.provide(routeLocationKey, reactive(reactiveRoute))}

基于 3.0 使用 composition API 时,没有 this 也没有混入,插件将充分利用 provide 和 inject 对外暴露一个组合函数即可,当然,没了 this之后也有不好的地方。

provide 和 inject 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

再来看下 vue-next-router 中 install() 是什么样的

export function createRouter(options: RouterOptions): Router {  // 省略大部分代码  const router: Router = {    currentRoute,    addRoute,    removeRoute,    hasRoute,    history: routerHistory,    ...    // install    install(app: App) {      applyRouterPlugin(app, this)    },  }  return router}

很简单,在 vue-next-router 提供的 install() 方法中调用 applyRouterPlugin 将 Vue 和 Router 作为参数传入。

最后在应用程序中使用 Router 时,只需要导入 createRouter 然后显示调用 use() 方法,传入 Vue,就可以在程序中正常使用了。

import { createRouter, createWebHistory } from 'vue-next-router'const router = createRouter({  history: createWebHistory(),  strict: true,  routes: [    { path: '/home', redirect: '/' }})const app = createApp(App)app.use(router)

没有全局 $router、$route

我们知道在 vue2-router 中,通过在 Vue 根实例的 router 配置传入 router 实例,下面这些属性成员会被注入到每个子组件。

this.$router - router 实例。

this.$route - 当前激活的路由信息对象。

但是 3.0 中,没有 this,也就不存在在 this.$router | $route 这样的属性,那么在 3.0 中应该如何使用这些属性呢?

我们首先看下源码暴露的 api 的地方:

// useApi.tsimport { inject } from 'vue'import { routerKey, routeLocationKey } from './injectionSymbols'import { Router } from './router'import { RouteLocationNormalizedLoaded } from './types'// 导出 useRouterexport function useRouter(): Router {  // 注入 router Router (key 与 上文的 provide 对应)  return inject(routerKey)!}// 导入 useRouteexport function useRoute(): RouteLocationNormalizedLoaded {  // 注入 路由对象信息 (key 与 上文的 provide 对应)  return inject(routeLocationKey)!}

源码中,useRouter 、 useRoute 通过 inject 注入对象实例,并以单个函数的方式暴露出去。

在应用程序中只需要通过命名导入的方式导入即可使用。

import { useRoute, useRouter } from 'vue-next-router'...setup() {  const route = useRoute()  const router = useRouter()  ...  // router -> this.$router  // route > this.$route  router.push('/foo')  console.log(route) // 路由对象信息}

除了可以命名导入 useRouter 、 useRoute 之外,还可暴露出很多函数,以更好的支持 tree-shaking(期待新版本的发布吧)。

NavigationFailureTypeRouterLinkRouterViewcreateMemoryHistorycreateRoutercreateWebHashHistorycreateWebHistoryonBeforeRouteLeaveonBeforeRouteUpdateparseQuerystringifyQueryuseLinkuseRouteuseRouter...
2ebc7c6f9822922fd67416da506e1091.png b1d5268914f5f1a95c2dcdf26fc5661e.png 8f50b814647468601a3230f4dc31e8db.png

你“在看”我吗?

51d0a7448b157b3f5bb1c25ac9b5d722.png
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/633203
推荐阅读
相关标签
  

闽ICP备14008679号