Vue Router 总结


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

Vue-Router路由模式

hash模式(默认)

hash是指 url 尾巴后的 # 号以及后面的字符。hash 虽然出现在url中,但不会被包括在http请求中,对后端完全没有影响,因此改变hash不会重新加载页面。

原理
基于浏览器的hashchange事件,地址变化时,通过window.location.hash 获取地址上的hash值;并通过构造Router类,配置routes对象设置hash值与对应的组件内容。

  • 优点
  1. hash值会出现在URL中, 但是不会被包含在Http请求中, 因此hash值改变不会重新加载页面
  2. hash改变会触发hashchange事件, 能控制浏览器的前进后退
  3. 兼容性好
  • 缺点
  1. 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL
  2. hash有体积限制,故只可添加短字符串
  3. 设置的新值必须与原来不一样才会触发hashchange事件,并将记录添加到栈中
  4. 每次URL的改变不属于一次http请求,所以不利于SEO优化
如何获取页面的hash变化

监听$route的变化

watch:{
$route:{
	handler:function(val,oldVal){
  	console.log(val);
  	}
	}
}

window.location.hash读取 # 值
window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。

history模式

当使用这种历史模式时,URL 会看起来很 “正常”,例如 https://example.com/user/id。

由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。

要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。

原理
基于HTML5新增的 pushState() 和 replaceState() 两个api,以及浏览器的 popstate 事件,地址变化时,通过window.location.pathname找到对应的组件。并配置routes对象,设置pathname值与对应的组件内容。

  • 优点
  1. pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL
  2. pushState() 设置的新URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中
  3. pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中
  4. pushState() 可额外设置 title 属性供后续使用
  5. 浏览器的进后退能触发浏览器的popstate事件,获取window.location.pathname来控制页面的变化
  • 缺点
  1. URL的改变属于http请求,借助 history.pushState 实现页面的无刷新跳转,因此会重新请求服务器。所以前端的 URL必须和实际向后端发起请求的 URL 一致。
  2. 如果用户输入的URL回车或者浏览器刷新或者分享出去某个页面路径,用户点击后,URL与后端配置的页面请求URL不一致,则匹配不到任何静态资源,就会返回404页面。所以需要后台配置支持,覆盖所有情况的候选资源,如果URL 匹配不到任何静态资源,则应该返回app 依赖的页面或者应用首页
  3. 兼容性差,特定浏览器支持

abstract模式

支持所有javascript运行模式。vue-router 自身会对环境做校验,如果发现没有浏览器的 API,路由会自动强制进入 abstract 模式。在移动端原生环境中也是使用 abstract 模式。

$route 和 $router

  • $route “路由信息对象”

包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。

  • $router “路由实例对象”

包括了路由的跳转方法,钩子函数等。

router-link 和 router-view

router-link 使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码,实质上最终会渲染成 a 链接。
点击 < router-link :to=“…”> 相当于调用 router.push(…) 。

在 router-link 上的 to 属性传值:

  • /path?参数名=值

接收传递过来的值: $route.query.参数名

  • /path/值/值 –> 需要路由对象提前配置 path: “/path/参数名”

接收传递过来的值: $route.params.参数名

router-view 子级路由显示,将显示与 url 对应的组件。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置:

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})

路由跳转方式

注意:在 Vue 实例中,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push

  • this.$router.push()

router.push 方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL

声明式编程式
< router-link :to=“…”>router.push(…)

this.$router.push( ) 方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串路径
router.push('/users/eduardo')

// 带有路径的对象
router.push({ path: '/users/eduardo' })

// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })

// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })

// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })

如果提供了 path,params 会被忽略,需要提供路由的 name 或手写完整的带有参数的 path :

const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
  • this.$router.replace()

作用类似于 router.push,唯一不同的是,它在导航时不会向 history 添加新记录

声明式编程式
< router-link :to=“…” replace>router.replace(…)

也可以直接在传递给 router.push 的 routeLocation 中增加一个属性 replace: true :

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
  • this.$router.go(n)

向前或者后跳转n个页面,n可以是正数也可以是负数

// 向前移动一条记录,与 router.forward() 相同
router.go(1)

// 返回一条记录,与 router.back() 相同
router.go(-1)

// 前进 3 条记录
router.go(3)

// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)

router.push、router.replace 和 router.go 是 window.history.pushStatewindow.history.replaceStatewindow.history.go 的翻版

params和query的区别

  • 用法
    query要用path来引入,params要用name来引入,接收参数分别是 this. r o u t e . q u e r y . n a m e 和 t h i s . route.query.name 和 this. route.query.namethis.route.params.name
  • url地址显示
    query更加类似于ajax中get传参,params则类似于post,前者在浏览器地址栏中显示参数,后者则不显示
  • 注意
    query刷新不会丢失query里面的数据, params刷新会丢失 params里面的数据

路由跳转和location.href的区别

  • 使用 location.href= /url来跳转,简单方便,但是刷新了页面;
  • 使用 history.pushState( /url ) ,不刷新页面,静态跳转;
  • 使用 router.push( /url ) 来跳转,使用了diff 算法,实现了按需加载,减少了 dom 的消耗;

使用 router 跳转和使用 history.pushState()没什么差别,因为vue-router就是用了 history.pushState() ,尤其是在history模式下。

路由配置参数

path跳转路径、component路径相对的组件

定义路由,每个路由都需要映射到一个组件:

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About },
]

name命名路由

  • 没有硬编码的 URL
  • params 的自动编码/解码
  • 防止 url 书写错误
  • 绕过路径排序(如显示一个)
const routes = [
  {
    path: '/user/:username',
    name: 'user',
    component: User,
  },
]

要链接到一个命名的路由,可以向 router-link 组件的 to 属性传递一个对象:

<router-link :to="{ name: 'user', params: { username: 'erina' }}">
  User
</router-link>

代码调用 router.push() 是一样的:

router.push({ name: 'user', params: { username: 'erina' } })

children嵌套路由

一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:

/user/johnny/profile                     /user/johnny/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+

通过 Vue Router,你可以使用嵌套路由配置来表达这种关系。
顶层的 router-view 渲染顶层路由匹配的组件:

<div id="app">
  <router-view></router-view>
</div>
const User = {
  template: '<div>User {{ $route.params.id }}</div>',
}

// 这些都会传递给 `createRouter`
const routes = [{ path: '/user/:id', component: User }]

一个被渲染的组件也可以包含自己嵌套的 <router-view>。例如,如果我们在 User 组件的模板内添加一个 <router-view>

const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `,
}

要将组件渲染到这个嵌套的 router-view 中,我们需要在路由中配置 children:

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // 当 /user/:id/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功
        // UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

props路由解耦

组件中使用 $route 会与路由紧密耦合,因为它只能用于特定的 URL,这限制了组件的灵活性。
将下面的代码

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]

替换成

const User = {
  // 请确保添加一个与路由参数完全相同的 prop 名
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]

这允许你在任何地方使用该组件,使得该组件更容易重用和测试。

redirect重定向路由

重定向通过 routes 配置来完成,下面例子是从 /home 重定向到 /:

const routes = [{ path: '/home', redirect: '/' }]

重定向的目标也可以是一个命名的路由:

const routes = [{ path: '/home', redirect: { name: 'homepage' } }]

也可以是一个方法,动态返回重定向目标:

const routes = [
  {
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
      return { path: '/search', query: { q: to.params.searchText } }
    },
  },
  {
    path: '/search',
    // ...
  },
]

也可以重定向到相对位置:

const routes = [
  {
    // 将总是把/users/123/posts重定向到/users/123/profile。
    path: '/users/:id/posts',
    redirect: to => {
      // 该函数接收目标路由作为参数
      // 相对位置不以`/`开头
      // 或 { path: 'profile'}
      return 'profile'
    },
  },
]

meta路由元信息

有时需要将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。
定义路由的时候你可以这样配置 meta 字段:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false }
      }
    ]
  }
]

路由导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。

全局前置守卫 router.beforeEach

全局前置守卫,进入路由之前调用。

每个守卫方法接收两个参数:

  • to: 即将要进入的目标
  • from: 当前导航正要离开的路由

next: 可选的第三个参数:

  • next() 没有参数表示放行
  • next(‘/login’),next(‘to’) 有参数表示中断当前导航,执行新的导航

确保 next 在任何给定的导航守卫中都被严格调用一次,它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。
错误用例:

// BAD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  // 如果用户未能验证身份,则 `next` 会被调用两次
  next()
})

正确写法:

// GOOD
router.beforeEach((to, from, next) => {
  if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
  else next()
})

返回值:

  • false: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
  • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或 name: ‘home’ 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 from 一样。

判断是否登录,未登录则跳转到登陆页面:

router.beforeEach((to,from,next)=>{
	let ifInfo = Vue.prototype.$common.getSession('userData');//判断是否登录
	if(!ifInfo){
  	//sessionStorage没有user信息
  	if(to.path == '/'){
    	//如果是登录页面直接next()
    	next();
    }else{//否则跳转到登录页面
    	Window.location.href = Vue.prototype.$loginUrl;
    }
	}else{
  	return next();
  }
})

全局解析守卫 router.beforeResolve

全局解析守卫,在 beforeRouteEnter 调用之后调用。

让用户可以访问自定义 meta 属性 requiresCamera 的路由:

router.beforeResolve(async to => {
  if (to.meta.requiresCamera) {
    try {
      await askForCameraPermission()
    } catch (error) {
      if (error instanceof NotAllowedError) {
        // ... 处理错误,然后取消导航
        return false
      } else {
        // 意料之外的错误,取消导航并把错误传给全局处理器
        throw error
      }
    }
  }
})

router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。

全局后置钩子 router.afterEach

全局后置钩子 进入路由之后调用,对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。和守卫不同的是,这些钩子不会接受 next 函数,也不会改变导航本身:

跳转之后滚动条回到顶部:

router.afterEach((to,from)=>{
	window.scrollTo(0,0);
})

路由独享的守卫 beforeEnter

你可以直接在路由配置上定义 beforeEnter 守卫:

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: (to, from) => {
      // reject the navigation
      return false
    },
  },
]

beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在从一个不同的路由导航时才会被触发。

你也可以将一个函数数组传递给 beforeEnter,这在为不同的路由重用守卫时很有用:

function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

组件内的守卫

你可以在路由组件内直接定义路由导航守卫(传递给路由配置的)

  • beforeRouteEnter
  • beforeRouteUpdate
  • beforeRouteLeave
const UserDetails = {
  template: `...`,
  beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
  },
  beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
  },
  beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
  },
}

beforeRouteEnter 守卫 不能 访问 this,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数:

beforeRouteEnter (to, from, next) {
  next(vm => {
    // 通过 `vm` 访问组件实例
  })
}

对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持 传递回调

beforeRouteUpdate (to, from) {
  // just use `this`
  this.name = to.params.name
}

beforeRouteLeave 通常用来预防用户在还未保存修改前突然离开:

beforeRouteLeave (to, from) {
  const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
  if (!answer) return false
}

完整的导航解析流程

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

动态路由

对路由的添加通常是通过 routes 选项来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由。

添加路由

动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。
它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由。

添加嵌套路由

要将嵌套路由添加到现有的路由中,可以将路由的 name 作为第一个参数传递给 router.addRoute(),这将有效地添加路由,就像通过 children 添加的一样:

router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })

等效于:

router.addRoute({
  name: 'admin',
  path: '/admin',
  component: Admin,
  children: [{ path: 'settings', component: AdminSettings }],
})

在导航中添加路由

如果你决定在导航守卫内部添加或删除路由,你不应该调用 router.replace(),而是通过返回新的位置来触发重定向:

router.beforeEach(to => {
  if (!hasNecessaryRoute(to)) {
    router.addRoute(generateRoute(to))
    // 触发重定向
    return to.fullPath
  }
})

删除路由

  • 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About })
// 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的
router.addRoute({ path: '/other', name: 'about', component: Other })
  • 通过调用 router.addRoute() 返回的回调:
const removeRoute = router.addRoute(routeRecord)
removeRoute() // 删除路由如果存在的话
  • 通过使用 router.removeRoute() 按名称删除路由:
router.addRoute({ path: '/about', name: 'about', component: About })
// 删除路由
router.removeRoute('about')

当路由被删除时,所有的别名和子路由也会被同时删除

查看现有路由

Vue Router 提供了两个功能来查看现有的路由:

  • router.hasRoute():检查路由是否存在。
  • router.getRoutes():获取一个包含所有路由记录的数组。

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。

用动态导入代替静态导入:

// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')

const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值