一.Vue-Router基本使用
在main.js中会将router对象注入到vue实例中,注入vue实例后会创建两个属性$router和$router。第一个是路由规则,第二个为路由实例
在路由规则中可以获取路由信息,在路由实例中可以获得路由方法比如push、go、replace等。此外若在开发插件过程中想要获取路由规则可以通过路由实例中的currentRoute获取路由的规则。
1、动态路由
动态路由有两种传参方式一种是直接通过$route.params.id获取到路由中的id占位,另一种是通过在路由规则中开启props,像父子组件传值一样获取到路由占位中的id
// router的index.js中
{
path: '/blog/:id',
name: 'Blog',
// 开启props,会把URL 中的参数传递给组件
// 组件中通过props来接受URL参数
props: true,
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/Blog.vue')
},
<template>
<div class="about">
<div>
<!-- 方式1:通过当前路由规则,获取数据 -->
通过当前路由规则获取:{{ $route.params.id }}
<br/>
<!-- 方式2: 路由规则中开启props传参 -->
通过开启 props 获取:{{ id }}
</div>
</div>
</template>
<script>
export default {
name: 'Blog',
props: ['id']
}
</script>
2、嵌套路由
若两个页面例如首页和详情页拥有相同的头部和尾部,则他们可以抽成一个例如Layout组件进行和并,在相同的头与尾中间用<router-view/>进行路由替换
在嵌套路由中,外部Layout中的路径会和children里面的路径进行合并,同时加载Layout组件和Index组件将他们合并到一起。在渲染的时候,会先加载Layout然后再加载Index然后合并在一起一同渲染出来
//嵌套路由
{
path: '/',
component: Layout,
children: [
{
name: 'index',
path: '',
component: Index
},
{
name: 'detail',
path: 'detail/:id',
props: true,
component: () => {'@/views/Detail.vue'}
}
]
}
3、Vue-Router编程式导航
(1)、push方法
push() {
this.$router.push('/')
},
(2)、replace方法
replace() {
this.$router.replace('/')
},
replace方法和push方法类似都可以跳转到相应的组件,但是replace不会记录跳转的历史,也就是说浏览器依然不能后退到上一个跳转过来的页面
(3)、go方法
go() {
this.$router.go(-2)
}
go方法是跳转到历史中的某一次,它可以是负数,也就是意味着后退,例如go(-2)后退两次记录
(4)传递参数
goDetail() {
this.$router.push({name: 'Detail', params:{ id: 1 }})
},
传递参数跳转先指定跳转组件的名称,在去传递参数
二、Hash和History模式区别
表现形式的区别:
- Hash模式:https://www.baidu.com/#/newsList?id=123456
- History模式: https://www.baidu.com/newsList/123456
Hash模式在#后面是url的地址同时会有一些和url无关的符号比如问好
History模式就是正常的url。
原理的区别:
- Hash模式是基于锚点,以及onhashchange事件
通过锚点的值作为路由地址,当地址发生变化的时候触发onhashchange事件,根据路径决定页面上呈现的内容
- History模式是基于Html5中的History API
History模式是基于history.pushState()和history.replaceState()两个方法。history.push和history.pushState方法的区别是push方法会在路径发生变化的时候向服务端发送请求,而pushState不会向服务端发送请求只是改变浏览器中地址栏的地址,然后记录地址内容。另外pushState方法是IE10之后才支持的会出现兼容性问题。
单页面应用history页面刷新流程:在某一个详情页面刷新后,先去服务器端请求这个页面,当服务器支持history路由模式时,因为Vue是一个单页面应用,请求的详情页面没有在服务器资源中找到则会默认返回index.html到浏览器,当浏览器接收到后会判断具体的路由地址然后去渲染某一个组件。
三、Vue-Router实现原理
回顾核心代码:
// router/index.js
// 注册插件
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
routes: [
{ name: 'home', path: '/', component: homeComponent }
]
})
// main.js
// 创建 Vue 实例, 注册 router 对象
new Vue ({
router,
render: h => h(App)
}).$mount('#app')
1.Vue.use这个方法它可以传入函数或者是对象,如果传入的是方法那么Vue会直接调用这个方法,如果传入的是一个对象那么他会去调用这个对象的install方法,这里面传入的是一个对象,想要实现Vue-Router就要去实现他的install方法
2.第二步,要去创建一个实例,所以VueRouter应该是一个构造函数或者是一个类,此处就应该要用类的方法去实现,并且此处还要有一个静态的install方法,因为这里是直接把VueRouter传入给Vue.use方法,类也是一个对象,因此要实现VueRouter是什么就清楚了:他是一个类,在这个类的里面有一个install的静态方法,VueRouter的构造函数需要传入一个参数并且该参数是一个对象形式,在对象中是一个路由规则,规则中最重要的是路由的地址和对应的组件
3.最后一步。是创建一个Vue实例并传入我们刚才创建好的router对象
(一)、实现VueRouter-install方法
let _Vue = null // 全局变量
export default class VueRouter {
// vuerouter类中的静态函数intsall函数传递两个参数
// Vue.use() 在调用install方法时 会传递两个参数 一个是Vue的构造函数 一个是可选的选项对象
// 这里不需要选项对象就不需要传递了
static install(Vue) {
//在这个静态方法里需要干哪些事情
// 1、要判断当前插件是否已经被安装,如果已经被安装那么就不需要重复去安装了
if(VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2、把Vue的构造函数记录到全局变量中来,因为当前方法是个静态方法而在这个方法中接受了Vue的构造函数,而以后我们的实例方法也要用到
//(接上)Vue的全局构造函数。
_Vue = Vue
// 3、把注册Vue实例时传入的router对象给他注入到所有的vue实例上,平时使用的this.$router,就是在这个第三步注入到Vue实例上的,并且我们所有
//(接上)的组件也全都是Vue的实例
// (如果想让所有的实例都共享一个成员方法那么就要用原型的形式)
// 混入(为了this能指向Vue实例) 给所有实例都混入一个beforeCreate钩子函数,在这个钩子函数中就可以获取到Vue实例,然后给他的原型上注入$router这个成员属性
// 因为每一个组件都是一个Vue原型实例,所以之后每一个组件都会执行beforeCreate这个钩子函数
_Vue.mixin({
beforeCreate() {
// 因为获取的是传入的选项router 所以获取的是实例的选项this.$options,从选项中获取router这个属性
// 因为混入每一个组件都会执行一次这个钩子会执行很多次,这里要做一个判断只有给实例传入router的时候才会执行,如果不传入则视为普通组件就不知行了
if(this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
(二)、VueRouter的构造函数
constructor (options) {
// options传入的是路由规则
this.options = options
// routeMap是一个键值对对象,用来解析传入的路由规则。键为路由地址,值为路由组件之后会根据路由地址在routeMap中找到对应的路由组件然后渲染到浏览器
this.routeMap = {}
// data是一个响应式的的对象里面current属性存储当前的路由地址,Vue中的observable方法可以创建一个响应式的属性
this.data = _Vue.observable({
current: '/' //默认是/根目录
})
}
(三)、VueRouter-- createRouteMap方法
createRouteMap () {
//遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
(四)、 VueRouter-- initComponent方法
initComponents (Vue) {
// 创建router-link组件,用Vue的component方法创建一个组件,第一个传入的是组件名字,第二个传入的是可选选项
Vue.component('router-link',{
// router-link会接收一个传入的属性to 外部传入的属性用props来接受,传入的名字是to属性是string字符串
props: {
to: String
},
// h函数去创建虚拟dom
render (h) {
// h函数会传递三个值 一个是选择器这里是A标签第二个是创建的dom对象设置的一些属性,通过attrs这个属性设置,第三个是内容传递的默认没有取名字的插槽
return h('a', {
// 要阻止超链接的后续点击事件(默认行为),因为跳转只想让标签地址改变不与服务器进行连接跳转,只改变浏览器地址栏且记录历史采用pushState方法
attrs: {
href: this.to
},
// 设置属性用attrs,设置事件用on
on: {
click: this.clickHandler // 不加小括号 加小括号是调用事件,不加是注册
}
// Vue实例的$slots方法获取插槽名字.default获取默认插槽
}, [this.$slots.default])
},
methods: {
// 这个点击事件处理两件事,第一件事是调用pushState方法改变浏览器的地址栏且不向服务器发送请求,第二件事是加载对应的组件
clickHandler (e) {
// pushState传入三个参数第一个是传给后续popState的对象参数 第二个是网页标题 第三个是要跳转的地址
history.pushState({}, '', this.to)
//加载对应组件 router-link是一个组件也是一个实例,在注册插件的时候已经把$router这个对象挂载到构造函数的原型属性上,所以所有的Vue实例都可以访问$router这个对象
// 因为data是一个响应式的属性所以当他的值改变,同时视图也发生改变重新加载router-view组件吧对应的组件渲染到视图上来
this.$router.data.current = this.to
// 这个方法阻止事件的默认行为
e.preventDefault()
}
}
// template最终会渲染成一个a标签,href绑定到上面传入的to,中间用插槽占位替换链接的文字
// template: '<a :href="to"><slot></slot></a>'
})
const self = this
Vue.component('router-view', {
render (h) {
// 寻找当前路由地址对应的组件
const component = self.routeMap[self.data.current]
// h函数可以把我们的组件转换成虚拟dom然后返回
return h(component)
}
})
}
(五)VueRouter--initEvent
initEvent () {
// 这个方法用来注册popState事件,处理浏览器点击前进和后退改变路由加载对应组件的方法
// 第一个值是事件名称 第二个是事件处理函数
window.addEventListener('popstate', () => {
// 让响应式里的路径等于现在浏览器地址栏里面的路径,让他发生改变
this.data.current = window.location.pathname
})
}
完整代码:
let _Vue = null // 全局变量
export default class VueRouter {
// vuerouter类中的静态函数intsall函数传递两个参数
// Vue.use() 在调用install方法时 会传递两个参数 一个是Vue的构造函数 一个是可选的选项对象
// 这里不需要选项对象就不需要传递了
static install(Vue) {
//在这个静态方法里需要干哪些事情
// 1、要判断当前插件是否已经被安装,如果已经被安装那么就不需要重复去安装了
if(VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2、把Vue的构造函数记录到全局变量中来,因为当前方法是个静态方法而在这个方法中接受了Vue的构造函数,而以后我们的实例方法也要用到
//(接上)Vue的全局构造函数。
_Vue = Vue
// 3、把注册Vue实例时传入的router对象给他注入到所有的vue实例上,平时使用的this.$router,就是在这个第三步注入到Vue实例上的,并且我们所有
//(接上)的组件也全都是Vue的实例
// (如果想让所有的实例都共享一个成员方法那么就要用原型的形式)
// 混入(为了this能指向Vue实例) 给所有实例都混入一个beforeCreate钩子函数,在这个钩子函数中就可以获取到Vue实例,然后给他的原型上注入$router这个成员属性
// 因为每一个组件都是一个Vue原型实例,所以之后每一个组件都会执行beforeCreate这个钩子函数
_Vue.mixin({
beforeCreate() {
// 因为获取的是传入的选项router 所以获取的是实例的选项this.$options,从选项中获取router这个属性
// 因为混入每一个组件都会执行一次这个钩子会执行很多次,这里要做一个判断只有给实例传入router的时候才会执行,如果不传入则视为普通组件就不知行了
if(this.$options.router) {
_Vue.prototype.$router = this.$options.router
// 当插件注册完成后调用初始化的方法---解析键 值对、创建router-link组件
this.$options.router.init()
}
}
})
}
constructor (options) {
// options传入的是路由规则
this.options = options
// routeMap是一个键值对对象,用来解析传入的路由规则。键为路由地址,值为路由组件之后会根据路由地址在routeMap中找到对应的路由组件然后渲染到浏览器
this.routeMap = {}
// data是一个响应式的的对象里面current属性存储当前的路由地址,Vue中的observable方法可以创建一个响应式的属性
this.data = _Vue.observable({
current: '/' //默认是/根目录
})
}
init () {
// 把两个初始化用的组件包裹起来让外界使用更方便
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
createRouteMap () {
//遍历所有的路由规则,把路由规则解析成键值对的形式存储到routeMap中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
})
}
initComponents (Vue) {
// 创建router-link组件,用Vue的component方法创建一个组件,第一个传入的是组件名字,第二个传入的是可选选项
Vue.component('router-link',{
// router-link会接收一个传入的属性to 外部传入的属性用props来接受,传入的名字是to属性是string字符串
props: {
to: String
},
// h函数去创建虚拟dom
render (h) {
// h函数会传递三个值 一个是选择器这里是A标签第二个是创建的dom对象设置的一些属性,通过attrs这个属性设置,第三个是内容传递的默认没有取名字的插槽
return h('a', {
// 要阻止超链接的后续点击事件(默认行为),因为跳转只想让标签地址改变不与服务器进行连接跳转,只改变浏览器地址栏且记录历史采用pushState方法
attrs: {
href: this.to
},
// 设置属性用attrs,设置事件用on
on: {
click: this.clickHandler // 不加小括号 加小括号是调用事件,不加是注册
}
// Vue实例的$slots方法获取插槽名字.default获取默认插槽
}, [this.$slots.default])
},
methods: {
// 这个点击事件处理两件事,第一件事是调用pushState方法改变浏览器的地址栏且不向服务器发送请求,第二件事是加载对应的组件
clickHandler (e) {
// pushState传入三个参数第一个是传给后续popState的对象参数 第二个是网页标题 第三个是要跳转的地址
history.pushState({}, '', this.to)
//加载对应组件 router-link是一个组件也是一个实例,在注册插件的时候已经把$router这个对象挂载到构造函数的原型属性上,所以所有的Vue实例都可以访问$router这个对象
// 因为data是一个响应式的属性所以当他的值改变,同时视图也发生改变重新加载router-view组件吧对应的组件渲染到视图上来
this.$router.data.current = this.to
// 这个方法阻止事件的默认行为
e.preventDefault()
}
}
// template最终会渲染成一个a标签,href绑定到上面传入的to,中间用插槽占位替换链接的文字
// template: '<a :href="to"><slot></slot></a>'
})
const self = this
Vue.component('router-view', {
render (h) {
// 寻找当前路由地址对应的组件
const component = self.routeMap[self.data.current]
// h函数可以把我们的组件转换成虚拟dom然后返回
return h(component)
}
})
}
initEvent () {
// 这个方法用来注册popState事件,处理浏览器点击前进和后退改变路由加载对应组件的方法
// 第一个值是事件名称 第二个是事件处理函数
window.addEventListener('popstate', () => {
// 让响应式里的路径等于现在浏览器地址栏里面的路径,让他发生改变
this.data.current = window.location.pathname
})
}
}