>  기사  >  웹 프론트엔드  >  Vue-Router의 기본 지식 및 작동 원리

Vue-Router의 기본 지식 및 작동 원리

亚连
亚连원래의
2018-05-29 17:18:421712검색

이 글은 주로 vue-router와 관련된 기본 지식과 단일 페이지 애플리케이션의 작동 원리를 소개합니다. 도움이 필요한 친구가 참고하면 됩니다.

서문

오늘 인터뷰에서 Vue의 동적 라우팅에 대해 질문받았습니다. , 그리고 나는 대답도 하지 않았습니다. 다가오는 것은 드문 문제처럼 느껴지지 않습니다. 나는 오랫동안 vue-router의 문서를 읽지 않았으며 사용된 많은 것들과 개념이 일치하지 않습니다. 돌아와서 동적 라우팅이 무엇인지 보고 어리둥절했습니다. Vue-Router 관련 지식을 정리할 필요가 있을 것 같습니다. 너무 당황스럽습니다.

단일 페이지 애플리케이션의 작동 원리

단일 페이지 애플리케이션의 작동 원리는 브라우저 URL의 # 다음에 해시 변경을 통해 페이지를 여러 개의 작은 모듈로 나누는 것입니다. 해시를 변경하고 수정하려면 페이지에 우리가 보고 싶은 콘텐츠가 표시되도록 하세요.

그렇다면 해시가 다른 이유는 무엇이며 페이지 표시에 영향을 미치는 이유는 무엇입니까? 여기서 브라우저는 무엇을 합니까? 과거에는 # 뒤의 콘텐츠가 일반적으로 앵커로 사용되었지만, 현재 라우팅과 어떻게 다른가요? (하나의 경로를 표시하면 다른 경로가 숨겨질 것이라고 생각합니다. 사실입니까?) 이 의심에 대해서는 나중에 살펴보고 글을 쓰겠습니다. 지금 가장 중요한 것은 기본 개념을 익히는 것입니다.

Text

vue-router를 추가할 때 해야 할 일은 구성 요소를 경로에 매핑한 다음 vue-router에 렌더링할 위치를 알려주는 것입니다.

시작하기

//*** router-link 告诉浏览器去哪个路由
//*** router-view 告诉路由在哪里展示内容
<p id="app">
 <h1>Hello App!</h1>
 <p>
 <!-- 使用 router-link 组件来导航. -->
 <!-- 通过传入 `to` 属性指定链接. -->
 <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
 <router-link to="/foo">Go to Foo</router-link>
 <router-link to="/bar">Go to Bar</router-link>
 </p>
 <!-- 路由出口 -->
 <!-- 路由匹配到的组件将渲染在这里 -->
 <router-view></router-view>
</p>
// 1. 定义(路由)组件。
// 可以从其他文件 import 进来
const Foo = { template: &#39;<p>foo</p>&#39; }
const Bar = { template: &#39;<p>bar</p>&#39; }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
// 我们晚点再讨论嵌套路由。
const routes = [
 { path: &#39;/foo&#39;, component: Foo },
 { path: &#39;/bar&#39;, component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置
// 你还可以传别的配置参数, 不过先这么简单着吧。
const router = new VueRouter({
 routes // (缩写)相当于 routes: routes
})
// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
 router
}).$mount(&#39;#app&#39;)
// 现在,应用已经启动了!

동적 라우팅 매칭

은 매개변수마다 서로 다른 구성요소 내용을 표시하므로 동일한 구성요소와 동일합니다. 실제로는 vue-router

const router = new VueRouter({
 routes: [
 // 动态路径参数 以冒号开头
 { path: &#39;/user/:id&#39;, component: User }
 ]
})
의 라우팅 경로에 "동적 경로 매개변수"를 사용하는 것입니다.

그런 다음 uesr/001 user/002 를 입력합니다. 실제로는 동일한 경로를 입력합니다. 다른 매개변수에 따라 콘텐츠 페이지에 다른 매개변수가 표시될 수 있습니다. . 일반적으로 적용 가능한 시나리오: 목록, 권한 제어uesr/001 user/002 其实是进入的同一个路由,可以根据参数的不同在内容页展示不同的内容。一般适用场景:列表,权限控制

定义的时候用: 表示是动态路由

使用 {{ $route.params.id }}

정의 시 사용: 동적 라우팅을 나타냅니다

이 경로의 매개변수 내용을 가져오려면 {{ $route.params.id }} 를 사용하세요

/user/foo에서 /user/bar로 이동하는 것과 같은 경로 매개변수를 사용하는 경우 원래 구성 요소 인스턴스가 재사용됩니다. 두 경로 모두 동일한 구성 요소를 렌더링하므로 이를 파괴하고 다시 만드는 것보다 재사용하는 것이 더 효율적입니다. 그러나 이는 구성 요소의 수명 주기 후크가 더 이상 호출되지 않음을 의미하기도 합니다.

컴포넌트를 재사용할 때 라우팅 매개변수의 변경에 응답하려면 $route 객체를 보면 됩니다.

const User = {
 template: &#39;...&#39;,
 watch: {
 &#39;$route&#39; (to, from) {
  // 对路由变化作出响应...
 }
 }
}

때로는 동일한 경로가 여러 경로와 일치할 수 있습니다. 이때는 일치 우선순위는 경로의 정의 순서를 기반으로 합니다. 먼저 정의한 사람이 가장 높은 우선순위를 갖습니다. nested rounding route route route

r
//路由里面也会出现 <router-view> 这是嵌套路由展示内容的地方
const User = {
 template: `
 <p class="user">
  <h2>User {{ $route.params.id }}</h2>
  <router-view></router-view>
 </p>
 `
}
//定义路由的时候在 加children 子路由属性
const router = new VueRouter({
 routes: [
 { path: &#39;/user/:id&#39;, component: User,
  children: [
  {
   // 当 /user/:id/profile 匹配成功,
   // UserProfile 会被渲染在 User 的 <router-view> 中
   path: &#39;profile&#39;,
   component: UserProfile
  },
  {
   // 当 /user/:id/posts 匹配成功
   // UserPosts 会被渲染在 User 的 <router-view> 中
   path: &#39;posts&#39;,
   component: UserPosts
  }
  ]
 }
 ]
})

는 빈 경로가 지정되지 않으면 빈 경로 컨텐츠가 표시됩니다. 프로그래밍 방식 탐색

Declarative: 327e3535b9e0e9f12607bd1fd4e603ad

Programmatic: router.push(...)

프로그래밍 방식 푸시는 탐색으로 이해될 수 있다고 상상할 수 있습니다. 라우터 기록의 새 해시로 인해 경로가 변경됩니다

router.replace() 경로를 수정했지만 기록에 존재하지 않습니다router.go(n) JS의 window.history.go(n)와 약간 비슷합니다. 네이밍 라우팅은 각 경로에 이름을 정의하는 것입니다.


이름이 지정된 뷰

때로는 중첩된 디스플레이 대신 여러 뷰(형제)를 동시에 표시하고 싶을 때가 있습니다. 예를 들어 사이드바(사이드 탐색)와 메인(메인 콘텐츠)의 두 가지 뷰가 있는 레이아웃을 생성하세요. 이번에는 명명된 뷰가 유용하게 사용되는 곳입니다. 단일 콘센트를 갖는 대신 인터페이스에 개별적으로 명명된 여러 개의 뷰를 가질 수 있습니다. router-view가 이름을 설정하지 않으면 기본값은 기본값입니다.


const router = new VueRouter({
 routes: [
 {
  path: &#39;/user/:id&#39;, component: User,
  children: [
  // 当 /user/:id 匹配成功,
  // UserHome 会被渲染在 User 的 <router-view> 中
  { path: &#39;&#39;, component: UserHome },
  ]
 }
 ]
})

뷰는 하나의 구성 요소를 사용하여 렌더링되므로 동일한 경로의 경우 여러 뷰에는 여러 구성 요소가 필요합니다. 구성요소 구성을 올바르게 사용하십시오(s 포함).

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>


리디렉션 및 별칭

리디렉션도 경로 구성을 통해 수행됩니다. 다음 예에서는 /a에서 /b로 리디렉션합니다.


const router = new VueRouter({
 routes: [
  {
   path: &#39;/&#39;,
   components: {
    default: Foo,
    a: Bar,
    b: Baz
   }
  }
 ]
})

일반적으로 홈페이지는 다른 장소로 리디렉션될 수 있습니다.

리디렉션 대상은 이름이 지정된 경로일 수도 있습니다:

const router = new VueRouter({
 routes: [
  { path: &#39;/a&#39;, redirect: &#39;/b&#39; }
 ]
})


또는 메소드는 동적으로 리디렉션 대상:

const router = new VueRouter({
 routes: [
  { path: &#39;/a&#39;, redirect: { name: &#39;foo&#39; }}
 ]
})


"리디렉션"은 사용자가 /a에 액세스하면 URL이 /b로 대체되고 일치하는 경로는 /b가 되며, "별칭"은 무엇입니까? ?

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

上面对应的路由配置为:

const router = new VueRouter({
 routes: [
  { path: &#39;/a&#39;, component: A, alias: &#39;/b&#39; }
 ]
})


『别名』的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。

HTML5 History 模式

ue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

const router = new VueRouter({
 mode: &#39;history&#39;,
 routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。

给个警告,因为这么做以后,你的服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

const router = new VueRouter({
 mode: &#39;history&#39;,
 routes: [
  { path: &#39;*&#39;, component: NotFoundComponent }
 ]
})

或者,如果你使用 Node.js 服务器,你可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。

导航守卫

我的理解 就是组件或者全局级别的 组件的钩子函数

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

全局守卫

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
 // ...
})


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

to: Route: 即将要进入的目标 路由对象

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

next(‘/') 或者 next({ path: ‘/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: ‘home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

确保要调用 next 方法,否则钩子就不会被 resolved。

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
 // ...
})

路由独享的守卫

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

const router = new VueRouter({
 routes: [
  {
   path: &#39;/foo&#39;,
   component: Foo,
   beforeEnter: (to, from, next) => {
    // ...
   }
  }
 ]
})

这些守卫与全局前置守卫的方法参数是一样的。

组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

beforeRouteEnter 
beforeRouteUpdate (2.2 新增) 
beforeRouteLeave

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

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

完整的导航解析流程

导航被触发。
在失活的组件里调用离开守卫。
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

路由元信息

我的理解就是 他可以把路由的父路径都列举出来,完成一些任务,比如登录,user 组件需要登录,那么user下面的foo组件也需要,那么可以通过这个属性 来检测这个路由线上 的一些状态。

定义路由的时候可以配置 meta 字段:

const router = new VueRouter({
 routes: [
  {
   path: &#39;/foo&#39;,
   component: Foo,
   children: [
    {
     path: &#39;bar&#39;,
     component: Bar,
     // a meta field
     meta: { requiresAuth: true }
    }
   ]
  }
 ]
})

首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录

例如,根据上面的路由配置,/foo/bar 这个 URL 将会匹配父路由记录以及子路由记录。

一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。

下面例子展示在全局导航守卫中检查元字段:

router.beforeEach((to, from, next) => {
 if (to.matched.some(record => record.meta.requiresAuth)) {
  // this route requires auth, check if logged in
  // if not, redirect to login page.
  if (!auth.loggedIn()) {
   next({
    path: &#39;/login&#39;,
    query: { redirect: to.fullPath }
   })
  } else {
   next()
  }
 } else {
  next() // 确保一定要调用 next()
 }
})

数据获取

我的理解就是在哪里获取数据,可以再组件里面,也可以在组件的守卫里面,也就是组件的生命周期里面。

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。

导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。

从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。

导航完成后获取数据

当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。

假设我们有一个 Post 组件,需要基于 $route.params.id 获取文章数据:

<template>
 <p class="post">
  <p class="loading" v-if="loading">
   Loading...
  </p>

  <p v-if="error" class="error">
   {{ error }}
  </p>

  <p v-if="post" class="content">
   <h2>{{ post.title }}</h2>
   <p>{{ post.body }}</p>
  </p>
 </p>
</template>
export default {
 data () {
  return {
   loading: false,
   post: null,
   error: null
  }
 },
 created () {
  // 组件创建完后获取数据,
  // 此时 data 已经被 observed 了
  this.fetchData()
 },
 watch: {
  // 如果路由有变化,会再次执行该方法
  &#39;$route&#39;: &#39;fetchData&#39;
 },
 methods: {
  fetchData () {
   this.error = this.post = null
   this.loading = true
   // replace getPost with your data fetching util / API wrapper
   getPost(this.$route.params.id, (err, post) => {
    this.loading = false
    if (err) {
     this.error = err.toString()
    } else {
     this.post = post
    }
   })
  }
 }
}

在导航完成前获取数据

通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。

export default {
 data () {
  return {
   post: null,
   error: null
  }
 },
 beforeRouteEnter (to, from, next) {
  getPost(to.params.id, (err, post) => {
   next(vm => vm.setData(err, post))
  })
 },
 // 路由改变前,组件就已经渲染完了
 // 逻辑稍稍不同
 beforeRouteUpdate (to, from, next) {
  this.post = null
  getPost(to.params.id, (err, post) => {
   this.setData(err, post)
   next()
  })
 },
 methods: {
  setData (err, post) {
   if (err) {
    this.error = err.toString()
   } else {
    this.post = post
   }
  }
 }
}

在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

Angular使用操作事件指令ng-click传多个参数示例

JavaScript代码实现txt文件的上传预览功能

Angularjs实现控制器之间通信方式实例总结

위 내용은 Vue-Router의 기본 지식 및 작동 원리의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.