大白话理解和初步使用vue-router
TL;DR
- router其实就是让路径和组件一一对应
- 即便不使用vue-router也可以实现跳转,但总是手动处理历史记录
- vue-router的使用好处:历史记录、参数处理、路由钩子、权限控制、记录滚动条位置
- vue-router指南和vue-router的API
- 坑:历史模式懒加载的时候注意名字、子路由一般不用
/
router到底是什么
router,路由,我理解的其实就是,让不同的路径对应的不同的视图。在vue里,其实视图就相当于组件。也就是让不同的路径对应不同的组件。换言之,router就是规定了路径和组件一一对应。这句可能是理解vue-touter的核心。
先说说render,为下个标题铺垫
vue官网对render解释的很清楚,这边我简单的说下。 一般我们写vue组件的时候,会写template,但任何template都可以用render函数代替,事实上,vue就是把template转换为render函数的,之所以我们用template,是因为其易读易写。
怎么用render函数替换template呢?
简单的举个例子,你的template是<h1 :title="blogTitle">{{ blogTitle }}</h1>
,对应的render函数如下
render: function (createElement) { // `createElement`的参数一般有三个,第一个必填就是标签名或者组件名,第二个是这个标签的属性,第三个就是子节点。 return createElement('h1', {attrs:{title:this.blogTitle}}, [this.blogTitle]) }
createElement
其实更准确的名字是createNodeDescription
,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。也就是“虚拟节点 (virtual node)”,简写为“VNode”。“虚拟 DOM”是对由 Vue 组件树建立起来的整个 VNode 树的称呼。
即便不用vue-router,也可以实现单页面跳转的
开始总以为,要想不同路径就必须要vue-router
,其实不必。vue官方文档有个很好的demo,这边挪用下逻辑。如果你只是需要简单的路由,也是可以直接这么用的。
// <div id="app"><div> const Home = { template: "<p>home page</p>" }; const About = { template: "<p>about page</p>" }; const NotFound = { template: "<p>Page not found</p>" }; // 路由 路径和组件一一对应 const routes = {'/':Home,'about':About} var app = new Vue({ el: "#app", data: { // 拿到地址栏路径 currentRoute: window.location.pathname }, computed: { // 由routes得到路径对应的组件 ViewComponent() { return routes[this.currentRoute] || NotFound; } }, // 这边用render了 render(h) { return h(this.ViewComponent); } }); // 历史记录的处理 window.addEventListener('popstate', () => { app.currentRoute = window.location.pathname })
用vue-router有什么好处
- 自己写上面一套繁琐哇
- 不同路径很轻易对应不同组件,而且人家处理好了历史记录
- 轻易解决传参数问题
- 有路由钩子,控制什么情况去什么路径,比如不登录的话就去登录页面
- 甚至可以记住滚动条的位置
官网就是很好的学习路径
vue-router官网,以下我简单的总结下。代码示例统一在末尾。
vue-router
分两种模式,history
模式和hash
模式(地址栏有#),hash
模式利用#不会跳转,history
模式利用pushState
和popState
。- vue-router增加两个组件
router-link
和router-view
,<router-link to="/foo">Go to Foo</router-link>
相当于<a href="/foo">Go to Foo</a>
,事实上也的确会被渲染成a标签。router-view
是占位符的感觉,会将路径匹配的组件显示在这里。 - 任何组件内都可以通过
this.$router
访问所有路由,访问当前路由this.$route
,多个r就是多的意思,这么记着吧,打印可以看看这两个 - 动态路由也就是不同的路径对应同一个视图。
path: '*'
匹配所有路由,通常放在最后匹配404(感觉像express)。使用通配符的时候,this.$route.params.pathMatch = *所表示的部分
- 嵌套路由其实就是
/user/create /user/list
这种。注意的坑是,以/
开头的嵌套路径会被当作根路径,所以一般嵌套的路由是不要/
- js里跳转到不同的路径的话,
router.push(location, onComplete?, onAbort?)
相当于<router-link to="/foo">
,replace会替换掉当前的历史记录,go(n)表示前进或者后退。如果 history 记录不够用,那就默默地失败呗。 - 如果同个组件里有多个router-view,怎么显示,
router-view
跟slot相似,不写name的话,默认name是default,对应的router文件里,components变成对象,{name:组件}
- 别名也就是a路径的时候想要显示b路径的视图,换言之a路径也是b路径的别名,
{ path: '/b', component: B, alias: '/a' }
- 重定向也就是a路径的时候想要跳转到b路径,从而显示b路径的视图,
{ path: '/a', redirect: '/b' }
- history模式下前进后退是需要服务器作支持的
- 导航守卫就是希望去某个路径的时候能做些什么事,路由钩子的感觉,注意的是,参数或查询的改变并不会触发进入/离开的导航守卫,可以通过
watch $route
对象来应对这些变化,或在组件内使用beforeRouteUpdate
。一旦用钩子,请一定确保要调用next
方法,否则钩子就不会被resolved
。想要去每一个路径都要判断时用router.beforeEach(to,from,next)
,next('/user')
可以直接中断去另外的路径,next(false)
中断跳转。如果只是想在去某个路径的时候做些事,可以在对应的路径下,写beforeEnter(to,from,next)
。如果想要禁止用户在还未保存修改前突然离开,可以在组件内设置beforeRouteLeave (to, from , next)
,这里注意next的路径最好不是to.path不然会死循环=。=。 - 有些路径是需要登录才能进去的就需要配置meta了,routes 配置中的每个路由对象为 路由记录,路由记录可以是嵌套的(有children的时候),因此,当一个路由匹配成功后,他可能匹配多个路由记录。
/foo/bar
这个 URL 将会匹配父路由记录以及子路由记录。meta取的方式 遍历$route.matched 来检查路由记录中的 meta 字段 - 去不同路径的时候想要切换的特效,能用
<transition name="slide">
解决的就用这个吧 - 跳转路径的时候,希望用路径上面的id请求数据,这个可以在跳转前完成也可以在跳转后完成,如果请求很快的话,可以在跳转前,请求慢的话可以在跳转后,根据需要来。
- 控制滚动条的位置,
scrollBehavior (to, from, savedPosition) { // return 期望滚动到哪个的位置 }
怎么根据路径显示不同组件的的
- vue拿到 地址栏的路径(如
/user/user-list
) - => 找routes 那边的配置,从上到下找 (如
path:'/user'
),拿到components,(如找到{default:Foo,a:Bar}
) - => 因为/user就是第一级路径,所以直接将app.vue里面的
<router-view/>
替换成Foo组件
,<router-view name="a"/>
替换成Bar组件
- => 再继续,找
path:'/user'
下面的children,发现目标path:'user-list'
,拿到components,找到{default:Second,a:Zoo}
- => 因为
user-list
是user的children,所以将user.vue里面的<router-view/>
替换成Second组件
,<router-view name="a"/>
替换成Zoo组件
- 找不到的话会报错,当然一般都会配置404
示例代码
<!-- 示例1 --> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <div 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> </div> <script> // 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter) // 1. 定义 (路由) 组件。也就是视图!!!! // 可以从其他文件 import 进来 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定义路由。也就是什么路径显示什么视图(组件)!!! // 每个路由应该映射一个组件。 其中"component" 可以是 // 通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 // 我们晚点再讨论嵌套路由。 const routes = [ { path: '/foo', component: Foo }, // 懒加载模式,会在合适的时机加载 bar.js,webpackChunkName就是将此组件的代码命名为bar.js 看控制台的network { path: '/bar', component: () => import(/* webpackChunkName: "bar" */ './views/Bar.vue'), }, // 注意 404 { path: '*', component: () => import(/* webpackChunkName: "404" */ './views/404.vue'), }, ] ] // 3. 创建 router 实例,然后传 `routes` 配置。 // 你还可以传别的配置参数, 不过先这么简单着吧。 const router = new VueRouter({ routes // (缩写) 相当于 routes: routes }) // 4. 创建和挂载根实例。!!!挂载在根实例下,也就是任何组件内都可以通过this.$router知道内容 // 记得要通过 router 配置参数注入路由, // 从而让整个应用都有路由功能 const app = new Vue({ router }).$mount('#app') // 现在,应用已经启动了! </script>
// 示例1 js // Home.vue export default { computed: { username () { // 我们很快就会看到 `params` 是什么 return this.$route.params.username } }, methods: { goBack () { window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/') } } }
/* 示例2:有嵌套路由的话 */ 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 } ] } ] /* 示例3:router.push使用 */ // push的两种参数情况,尽量用路径的形式,用name的话有时候子路由可能不太方便 // -> /user/123 router.push({ path: `/user/${userId}` }) // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { userId }}) // replace router.replace({ path: 'register', query: { userId }}) router.go(1) router.go(-1) /* 示例4:router-view多个的情况 */ <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> { path: '/', components: { default: Foo, a: Bar, b: Baz } } /* 示例5:重定向和别名 */ const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } // { path: '/b', component: B, alias: '/a' } ] }) /* 示例6:导航守卫 */ const router = new VueRouter({ ... }) // router上面的钩子 router.beforeEach((to, from, next) => { next() // next(false) // next('/') }) router.afterEach((to, from) => { // ... }) const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, // route上的钩子 beforeEnter: (to, from, next) => { // ... } } ] }) // 组件内的钩子 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` } } /* 示例7:meta的使用 */ const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ] }) 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: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() // 确保一定要调用 next() } }) /* 示例8:跳转路径的特效 */ <transition> <router-view></router-view> </transition> const Foo = { template: ` <transition name="slide"> <div class="foo">...</div> </transition> ` } /* 示例9:根据路径的参数去请求数据 这边显示跳转之后请求数据 */ export default { data () { return { loading: false, post: null, error: null } }, created () { // 组件创建完后获取数据, // 此时 data 已经被 observed 了 this.fetchData() }, watch: { // 如果路由有变化,会再次执行该方法 '$route': 'fetchData' }, 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 } }) } } } /* 示例10:控制滚动条的位置 */ scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } }
写例子来实践
需求:
- 现在总共有四个页面 home profile user login,profile是个人中心的意思
- 然后 user下面有 create-user user-list
- 然后设置 只有登录之后 才能去 profile user 否则就跳转到 login
- 每个页面都有导航条
1.加四个视图和对应的路由
vue create router-apply
,先选择history模式好了- views增加 每个页面的视图
- router那边配置路径
- app那边就可以写导航条了
router-link router-view
- 坑:发现懒加载的情况下,路径不能写成变量,但是可以别名
2.user那边增加子页面
- views增加 create-user user-list
- router那边配置路径
- user.vue那边增加router-view
3.user子页面能相互跳转,增加的用户跳转的时候传递到user-list
- create-user 有input框和跳转按钮,跳转的时候,带着值,用query的方式
- 顺便还可以设置
/user
路径的时候跳转到/user/create-user
4.设置只有登录才能去profile和user
- profile和user路径设置meta
- 在router那边增加
beforeEach
钩子
核心代码展示
// main.js router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.needLogin)) { // this route requires auth, check if logged in // if not, redirect to login page. if (!isLogin) { next({ path: '/login', // 方便登录之后返回来 query: { redirect: to.fullPath } }) } else { next() } } else { next() // 确保一定要调用 next() } }) // router.js { path: '/user', name: 'user', component: () => import(/* webpackChunkName: "user" */ './views/User.vue'), children: [ { path: 'create-user', alias: '', component: () => import(/* webpackChunkName: "createUser" */ './views/CreateUser.vue') }, { path: 'user-list', component: () => import(/* webpackChunkName: "userList" */ './views/UserList.vue') } ], meta: { needLogin: true } }