Vue-Router的使用步骤
-
在
vue
项目中使用vue-router
的步骤:-
安装
vue-router
-
创建
router
对象- 使用
Vue.use
注册vue-router
组件 - 定义路由规则
- 根据路由规则创建
router
对象
import Vue from "vue"; import VueRouter from "vue-router"; // Vue.use注册组件,如果传入的是一个方法,则调用该方法注册组件,如果传入的是一个对象,则调用对象的install方法来注册组件 Vue.use(VueRouter); // 路由规则 const routes = [ { path: "/index", name: "index", component: () => import(/* webpackChunkName: "index" */ "../views/Index.vue") }, { path: "/blog", name: "blog", component: () => import(/* webpackChunkName: "blog" */ "../views/Blog.vue") }, { path: "/photo", name: "photo", component: () => import(/* webpackChunkName: "photo" */ "../views/Photo.vue") } ]; // 创建 router 对象 const router = new VueRouter({ routes }); export default router;
- 使用
-
创建
Vue
实例时,将router
对象传入import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; new Vue({ router, render: h => h(App) }).$mount("#app");
-
-
动态路由
- 两种实现方式
-
在路由规则中通过
:paramName
的形式声明动态路由参数,然后再组件中通过$route.params.paramName
来访问参数。// 路由规则配置 { name: "blog", path: "/blog/:id", component: () => import(/* webpackChunkName: "blog" */ "../views/Blog.vue") } // 组件中使用 <template> <div>Blog{{ $route.params.id }}</div> </template> <script> export default { name: 'blog' } </script>
-
在路由规则中通过
:paramName
的形式声明动态路由参数,设置props:true,route.params 将会被设置为组件属性。在组件中就可以组件属性的方式访问路由参数// 路由规则配置 { name: "photo", path: "/photo/:num", // props 向路由组件传参,可以是一个Boolean,对象,或函数 props: true, // 为true时,route.params 将会被设置为组件属性。 component: () => import(/* webpackChunkName: "photo" */ "../views/Photo.vue") } // 组件中使用 <template> <div>Photo {{ num }}</div> </template> <script> export default { name: 'Photo', props: ['num'] } </script>
-
- 路由规则配置中
props
的3种情况-
props
为true
,route.params
将会被设置为组件属性 -
props
是一个对象时,会按原样设置为组件属性// 路由规则配置 { name: "index", path: "/index", // props 向路由组件传参,可以是一个Boolean,对象,或函数 props: { a: 12, b: 32 }, // props是一个对象时,会按原样设置为组件属性 component: () => import(/* webpackChunkName: "index" */ "../views/Index.vue") } // 组件中使用 <template> <div>Index {{a}} {{b}}</div> </template> <script> export default { name: 'index', props: [ 'a', 'b', ] } </script>
-
props
是一个函数时,会按原样设置为组件属性// 路由规则配置 { name: "index", path: "/index", props: route => { console.log(route); return { a: 12, b: 32 }; }, // props是一个函数时,会将函数的返回值作为props的值 component: () => import(/* webpackChunkName: "index" */ "../views/Index.vue") } // 组件中使用 <template> <div>Index {{a}} {{b}}</div> </template> <script> export default { name: 'index', props: [ 'a', 'b', ] } </script>
-
- 两种实现方式
-
路由嵌套
-
在路由规则中配置
children
-
在父组件中添加
<router-view>
标签// 嵌套路由 const routes = [ { name: "index", path: "/index/:id", component: () => import(/* webpackChunkName: "index" */ "../views/Index.vue"), children: [ { path: "blog", component: () => import(/* webpackChunkName: "blog" */ "../views/Blog.vue"), children: [ { path: "photo", component: () => import(/* webpackChunkName: "photo" */ "../views/Photo.vue") } ] }, { path: "", component: () => import(/* webpackChunkName: "photo" */ "../views/Photo.vue") } ] } ]; // index.vue <template> <div> <div>Index {{ $route.params.id }}</div> <router-view></router-view> </div> </template> <script> export default { name: 'index', } </script> // blog.vue <template> <div> <div>Blog{{ $route.params.id }}</div> <router-view></router-view> </div> </template> <script> export default { name: 'blog' } </script>
-
-
编程式的导航
-
借助
router
的实例方法,通过编写代码来实现导航,在Vue
实例内部,你可以通过$router
访问路由实例。 -
router.push(location, onComplete?, onAbort?)
:导航到不同的URL
,会向history
栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的URL
。// 字符串 router.push('home') // 对象 router.push({ path: 'home' }) // 命名的路由,name必须是路由规则中存在的,这里的params可以在组件中通过$route.params访问 router.push({ name: 'user', params: { userId: '123' }}) // 带查询参数,变成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }})
如果提供了
path
,params
会被忽略const userId = '123' router.push({ name: 'user', params: { userId }}) // -> /user/123 router.push({ path: `/user/${userId}` }) // -> /user/123 // 这里的 params 不生效 router.push({ path: '/user', params: { userId }}) // -> /user
-
router.replace(location, onComplete?, onAbort?)
:跟router.push
很像,唯一的不同就是,它不会向history
添加新记录,而是替换掉当前的history
记录。 -
router.go(n)
:这个方法的参数是一个整数,意思是在history
记录中向前或者后退多少步,类似window.history.go(n)
。
-
-
hash
模式和history
模式的区别
hash
模式和history
模式都是客户端路由的实现方式,当路由发生变化时,js
会监听路由变化,展示不同的内容,不会向服务器发送请求。- 表现形式的区别
hash
模式路径中带有#
,#
后面的内容是路由的地址,可以通过?
来携带参数。https://abs.cs.com/#/detail?id=123
history
模式是正常的路径,不带有与路径无关的字符。需要服务器端的支持。https://abs.cs.com/detail/123
- 原理的区别
hash
模式是基于锚点以及onhashchange
事件。锚点的值作为路由地址,当路由发生变化时,会触发onhashchange
事件。history
模式是基于H5中的History API。IE10以后才支持history.pushState()
,不会向服务器发送请求,只会改变地址栏中的地址,并将其保存到历史记录里。history.replaceState()
history
模式的使用history
模式需要服务器的支持。单页应用中,服务端不存在http://demo.com/login
这样的地址,会返回找不到页面。所以在服务器端应该配置除了静态资源外都返回单页应用的index.html
。history
模式的开启,需要在创建router
对象时,将mode:'history'
传入。// 创建 router 对象 const router = new VueRouter({ mode: "history", routes });
node
服务器中添加history
模式支持,使用express
开发一个简单的node
服务器,代码如下:dist
是项目打包生成的代码目录const path = require("path"); // 导入处理 history 模式的模块 const history = require("connect-history-api-fallback"); // 导入 express const express = require("express"); const app = express(); // 注册处理history模式的中间件 app.use(history()); //处理静态资源的中间件,网站根目录 dist app.use(express.static(path.join(__dirname, "dist"))); // 开启服务器 app.listen(3000, () => { console.log("服务器启动,端口:3000"); });
- 在
nginx
服务器中开启对history
模式的支持。需要在nginx
的配置文件中的location
中添加try_files $uri $uri/ /index.html;
这行代码的意思是尝试从location / { root html; index index.html index.htm; try_files $uri $uri/ /index.html; }
$uri
中查找文件,其中$uri
表示当前请求的路径。如果找不到则从$uri/
(表示当前q请求的目录)中查找index.html
或index.htm
,如果还是找不到,则返回根目录的index.html
。
- 表现形式的区别
Vue-Router的原理实现
VueRouter
原理分析hash
模式URL
中#
后面的部分作为路径地址- 监听
hashchange
事件,#
后面部分变化时会触发该事件 - 根据当前路由地址找到对应组件重新渲染
history
模式- 通过
history.pushState
方法改变地址栏,不会向服务器发送请求,只会改变地址栏,并将地址保存到历史中。 - 监听
popstate
事件来监听历史地址的变化,在该事件的回调函数中记录改变的历史。pushState
和replaceState
方法不会触发该事件,只有点击浏览器的前进或后退按钮,调用back
、forward
、go
时才会触发。 - 根据当前路由地址找到对应组件重新渲染
- 通过
VueRouter
模拟实现,以history
模式为例-
首先根据
VueRoute
的使用分析,可以通过new
关键字创建实例对象,所以VueRoute
必须是一个类或函数。这里以类的方式实现。 -
确定
VueRoute
为一个类之后,由于使用Vue.use
时传入了一个对象,所以该对象中必须包含install
方法,因此类中应该包含一个install
静态方法。
该方法接收两个参数,第一个参数是Vue
的构造函数,第二个参数是Vue.use
安装插件时传入的可选的选项对象。
install
方法需要完成3
件事:-
判断是否已经安装过插件,避免重复安装
-
保存
Vue
构造函数,后面实例方法中需要用到,生成router-link
和router-view
组件时需要用到 -
将创建
Vue
实例时传入的router
对象注入到所有的Vue
实例中。所有的组件都是Vue
的实例。- 首先要能获取到
Vue
实例创建时传入的router
, 通过注册全局mixin
为所有组件注册一个beforeCreate
生命周期函数,在函数内部可以通过this.$options.router
访问到传入的router
- 其次需要所有
Vue
的实例都能访问到,在Vue
的prototype
中定义$router
来保存router
对象。 - 最后
beforeCreate
会调用多次,,只有传入了router
对象才需要设置
let _Vue = null; export default class VueRouter { /** * * @param {*} Vue Vue构造函数 * @param {*} option Vue.use时传入的选项参数 */ static install(Vue) { // 1.判断是否已经安装过,如果已经安装过,则直接返回,避免重复安装 if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 保存Vue _Vue = Vue; // 将Vue实例创建时传入的router对象注入到所有Vue实例中。 _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; } } }); } }
- 首先要能获取到
-
-
实现构造函数
- 首先要保存调用构造函数时传入的配置对象,这里保存在实例属性
options
中 - 初始化保存当前路由的对象
data
,该对象需要是一个响应式的对象,当路由变化时,需要自动加载组件。 - 保存
options
中传入的规则配置中的路径到展示组件的映射,保存为一个routeMap
对象,以路径为键,路径对应展示的组件为值。调用构造函数时需要初始化为一个空对象。
constructor(options) { // 保存配置对象 this.options = options; // 初始化保存当前路由的对象data为一个响应式的对象 this.data = _Vue.observable({ current: "/" }); // 初始化保存路径和展示组件映射关系的对象。 this.routeMap = {}; }
- 首先要保存调用构造函数时传入的配置对象,这里保存在实例属性
-
创建initRouteMap方法,生成路由配置规则中的路径和组件的映射关系,并保存到
routeMap
对象中。initRouteMap() { this.options.routes.forEach(route => { this.routeMap[route.path] = route.component; }); }
-
创建
router-link
和router-view
组件的initComponent
方法-
Vue-cli
默认使用未包含编译器的vue
版本,在创建组件时不能使用template
模板,要想使用包含编译器的vue
,需要在根目录下添加配置文件vue.config.js
,并将runtimeCompiler
设置为true
。module.exports = { runtimeCompiler: true };
-
如果不想使用带有编译器版本的
vue
(因为带有编译器大小会大10Kb
左右),可以使用render
函数来替换template
。 -
创建
router-link
组件,router-link
组件接收一个to
属性,并渲染一个a
标签。点击router-link组件时,不刷新浏览器,改变地址栏地址并渲染对应视图。- 通过
props
接收to
属性的值 render
函数中使用h创建a
标签,并通过this.$slots.default
来获取子元素内容- 为
a
标签添加点击事件,e.preventDefault
来阻止默认行为,防止从服务器获取内容浏览器刷新,然后调用window.history.pushSate
来改变地址栏地址,并记录到历史中。然后更新data.current
的值。
Vue.component("router-link", { props: { to: String }, methods: { aClick(e) { e.preventDefault(); window.history.pushState({}, "", this.to); // 修改地址栏中的地址,并将地址存入到历史中 _this.data.current = this.to; // 修改记录中的本地地址,并触发视图更新 } }, render(h) { return h( "a", { attrs: { href: this.to }, on: { click: this.aClick } }, [this.$slots.default] ); } });
- 通过
-
创建
router-view
组件,router-view
功能相对较简单,只是展示当前路径对应的视图。使用data
中的current
来获取当前的路由,data
是一个响应式对象,所以data
中的属性值变化时视图也会随即改变。Vue.component("router-view", { render(h) { const component = _this.routeMap[_this.data.current]; return h(component); } });
-
-
创建
initEvent
函数,注册popstate
事件监听
popstate
事件监听是为了当通过非router-link
点击及组件提供的方法改变路由地址的情况下及时更新视图。只要在回调函数中改变更新data.current
为最新的路由地址即可,响应式的data
会自动触发视图的更新。initEvent() { window.addEventListener("popstate", () => { this.data.current = window.location.pathname; // 将当前地址保存到响应式对象中,以此触发视图更新 }); }
-
将
initRouteMap
,initComponent
,initEvent
方法包装到一个init
方法中,关于init
方法在何时执行,init
执行时必须已经保存了Vue
构造函数,initComponent
执行时需要,并且需要已经完成构造函数的调用,initRouteMap
需要调用构造函数时传入的路由规则。综上init
应该在混入的beforeCreate
函数中调用。static install(Vue) { // 1.判断是否已经安装过,如果已经安装过,则直接返回,避免重复安装 if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 保存Vue _Vue = Vue; // 将Vue实例创建时传入的router对象注入到所有Vue实例中。 _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; this.$options.router.init(); //通过保存的VueRouter实例来调用init方法,来完成初始化 } } }); }
-