80324291 2019-12-07
首先抛出这样一个问题,vue-router是用来做什么的?
这里不着急回答,也不准备在这篇文章里回答。这篇文章仅总结一些使用心得,其实总结完所有关于vue-router的内容后,整篇文章也许就刚好能回答这个问题了。
单纯使用Vue.js,我们可以通过组合组件来组成应用,不同的页面有不同的地址,路由依靠链接跳转。这显然不是单页应用,因为会有页面刷新。
当要把vue-router引入进来,我们需要做的是,将组件映射到路由,然后告诉路由在哪里渲染组件内容。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>vue-router</title> </head> <body> <div id="app"> <h1>Hello World!</h1> <p> <!--页面导航元素,默认被渲染为a标签--> <router-link to="user">toUser</router-link> <!--路由渲染出口,路由匹配到的组件内容全部渲染到该节点下--> <router-view></router-view> </p> </div> <script src="/dist/build.js"></script> </body> </html>
import Vue from 'vue'; import VueRouter from 'vue-router'; import indexPage from './index.vue'; import userPage from './user.vue'; // 注册路由插件 Vue.use(VueRouter); // 配置路由 const routes = [ { path: '', component: indexPage, }, { path: '/user', component: userPage, }];
// 创建Router实例 const router = new VueRouter({ routes, });
// 创建和挂载Vue根实例 const app = new Vue({ router, }); app.$mount('#app');
router-link和router-view是两个功能性内置组件。router-link默认被渲染为a标签,负责路由跳转功能;router-view是组件内容被渲染的位置。
js方面代码里的注释已经写得很清楚。
动态路由其实又可以叫做路由传参。
const router = new VueRouter({ routes: [ // 动态路径参数 以冒号开头 { path: '/user/:id', component: User } ] })
形如上述形式的路径即为动态路由,冒号后是参数,可以跟多段参数,每个参数都被设置到this.$route.params中。
注意/user/:id和/user/:name,当参数变化时,组件会被复用,因此组件生命周期钩子不会被再次调用。复用组建时,可以通过监听$route对象的变化来监测路由是否变化。
路由钩子beforeRouterUpdate也会执行。
vue-router 使用 path-to-regexp 作为路径匹配引擎,假如路径很复杂可以学习高级的匹配模式。但是路径一般不应设计的太复杂,如果太复杂,应该考虑如何简化。
有时候想同时(同级)展示多个视图,例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。
<router-view class="view one"></router-view> <router-view class="view two" name="sidebar"></router-view> <router-view class="view three" name="header"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置(带上 s):
routes: [ { path: '/', components: { default: Foo, a: SideBar, b: Header } } ]
定义路由的时候可以配置 meta 字段:
routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ]
除了使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。
router.push(location)
想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
当你点击 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)。
router.replace(location)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
router.go(n);
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
异步组件模式:
const Foo = resolve => { // require.ensure 是 Webpack 的特殊语法,用来设置 code-split point // (代码分块) require.ensure(['./Foo.vue'], () => { resolve(require('./Foo.vue')) }) }
Amd风格的require模式:
const routes = [ { path: '', component: resolve => require(['./index.vue'],resolve), }, { path: '/user', component: resolve => require(['./user.vue'],resolve), }];
ensure模式写起来有点繁琐,我更倾向于后面一种写法。
钩子(Hook),早期编程可能有个概念叫句柄,不知道将两者类比而且强行归为一类是不是合适。钩子的用处是在某个特定流程中的不同时机暴露出一些函数,使得用户可以通过覆写这些函数实现在原有位置执行自己的代码逻辑的功能。
vue-router中的导航钩子按定义位置不同(执行时机也不同)分为全局钩子、路由级钩子和组件级钩子。
全局钩子
全局钩子有三个,分别是beforeEach、beforeResolve和afterEach,在路由实例对象注册使用;
路由级钩子
路由级钩子有beforeEnter,在路由配置项中项定义;
组件级钩子
组件级钩子有beforeRouteEnter、beforeRouteUpdate和beforeRouteLeave,在组件属性中定义;
import Vue from 'vue'; import VueRouter from 'vue-router'; // Vue中插件必须use注册 Vue.use(VueRouter); // 路由配置项,此处是路由级钩子的定义 const routes = [{ path: '/', component: resolve => require(['./index.vue'], resolve), keepAlive: true, }, { path: '/user/:userName', keepAlive: true, beforeEnter(to,from,next){ console.log('router beforeEnter'); next(); }, component: resolve => require(['./user.vue'], resolve), }]; // 实例化路由对象 const router = new VueRouter({ routes }); // 全局钩子 router.beforeEach((to,from,next)=>{ console.log('global beforeEach') next(); }); router.beforeResolve((to,from,next)=>{ console.log('global beforeResolve') next(); }); router.afterEach((to,from,next)=>{ console.log('global afterEach') }); // 实例化Vue对象并挂载 new Vue({ router }).$mount('#app'); user.vue <template> <div> <h1>{{ msg }}</h1> <p>我是:{{userName}}</p> </div> </template> <script> export default { name: 'user', data () { return { msg: '这里是 User Page.', userName: '叶落' }; }, methods: {}, mounted () { var me = this; me.userName = me.$route.params.userName; console.log('user mounted.'); }, beforeRouteEnter (to, from, next) { console.log('component beforeRouteEnter'); next(); }, beforeRouteUpdate (to, from, next) { console.log('component beforeRouteUpdate'); next(); }, beforeRouteLeave(to,from,next){ console.log('component beforeRouteLeave'); next(); } }; </script>
由首页进入user页面:
global beforeEach > router beforeEnter > component beforeRouteEnter > global beforeResolve > global afterEach > mounted
由user回到首页:
component beforeRouteLeave => global beforeEach => global beforeResolve => global afterEach
排除beforeRouteUpdate,其余六个导航钩子的执行时机其实很好理解。大体按照leave、before、enter、resolve、after的顺序并全局优先的思路执行。beforeRouteUpdate的触发是在动态路由情形下,比如 path: ‘/user/:userName‘ 这条路由,当页面不变更只动态的改变参数userName时,beforeRouteUpdate便会触发。
总结:使用vue组件拼凑成整个应用,每个页面是独立的,路由依靠链接跳转,会刷新页面。使用vue-router则可以不刷新页面加载对应组件,hash和history模式模拟路径变化,不刷新页面。