第一次发文,来自 vue 学习记录,后续持续更新学习内容,鞭策自己。加油鸭!
vue-router
它和Vue.js的核心深度集成,让构建单页面应用变的易如反掌。它并不能用在其他库中,比如react。
核心步骤:
-
使用vue-router插件,router.js
import Router from 'vue-router' Vue.use(Router)
-
创建Router实例,router.js
export default new Router({...})
-
在根组件上添加该实例,main.js
import router from './router' new Vue({ router, }).$mount("#app");
-
添加路由视图,App.vue
导航:
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
跳转:
this.$router.push('/')
this.$router.push('/about')
问题:
-
Vue.use(Router)
的时候内部会做什么事情? -
为什么要把
router
作为选项,设置到选项中? -
router-link
为什么可以直接使用,而不需要注入?
vue-router源码实现
单⻚面应用程序中,url发生变化时候,不能刷新,显示对应视图内容
需求分析
-
spa页面不能刷新
-
hash
比如:#/about
-
监听
hashchange
事件 -
会和
component
产生映射关系,当hash改变只需要把对应组件渲染到router-view
中 -
History api /about
-
点击跳转时,url虽然改变了,但是浏览器并不刷新,就可以拦截下,并处理跳转
-
根据url显示对应的内容
-
router-view
-
数据响应式:
current
变量持有url
地址,一旦变化,动态重新执行render
任务拆分
-
实现一个插件
-
实现
VueRouter
类 -
处理路由选项
-
监控
url
变化,hashchange
-
响应这个变化
-
实现
install
方法 -
$router
注册 -
注册两个全局组件(
router-view
、router-link
)
代码实现
此案例实现的是 hash
方式
node版本为:10.23.0
先使用vue-cli创建一个vue2有vue-router的项目。目录结构:
package.json:
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
}
}
创建一个自己的 router 目录,我这里命名为 lrouter,并新建一个 index.js 文件
修改 main.js 文件里的 router 文件路径为 ./lrouter
// main.js
import Vue from 'vue'
import App from './App.vue'
// import router from './router'
import router from './lrouter'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
修改 lrouter 文件里的 index.js 内容:
// lrouter/index.js 此文件只是修改了router/index.js文件的Vue-router的引用路径
import Vue from 'vue'
import VueRouter from './lvue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// 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/About.vue')
}
]
const router = new VueRouter({
routes
})
export default router
创建 lvue-router.js 文件
// lrouter/lvue-router.js
// 1.实现一个插件
// 2.实现VueRouter:处理选项、监控url变化、动态渲染
class VueRouter {
}
// 因为在lrouter/index.js文件中 VueRouter 需要Vue.use来使用,因此需要实现一个install方法
VueRouter.install = function () {
}
export default VueRouter
当 Vue.use
使用插件的时候会将 vue 传递到插件中。VueRouter类中需要使用Vue,因此需要在外层先创建一个Vue变量
let Vue;
class VueRouter {
// Vue要在这里使用
}
VueRouter.install = function (_vue) {
Vue = _vue
}
export default VueRouter
这里先说一下问题2:为什么要把router
作为选项,设置到选项中?
因为我们在组件中经常会使用this.$router.push()
,如果想这样使用就需要将$router
挂载到Vue
实例上。挂载到Vue
实例上一般使用Vue.prototype.$router = router
来挂载。
现在有一个问题,就是install
方法调用的时间点非常早。从 lrouter/index.js 文件看出,Vue.use(VueRouter)
要早与 new VueRouter
,按照同步代码执行顺序,在install
方法中是获取不到VueRouter
实例的,现在利用全局混入尝试延迟调用:
VueRouter.install = function (_vue) {
Vue = _vue
// 利用全局混入来延时调用后续代码
Vue.mixin({
beforeCreate() {
// 以后每个组件都会调用该方法, 避免重复调用
if (this.$options.router) {
// 此时的上下文this是当前组件实例
Vue.prototype.$router = this.$options.router
}
}
})
}
上述代码完成了挂载$router
,下面实现注册两个全局组件,在 install 方法中,加入如下代码:
Vue.component('router-view', {
// 注意:这里不能写template,因为目前的vue版本是运行时(runtime-onle)版本,不带编译器的
render(h) {
return h('div', 'router-view')
}
})
Vue.component('router-link', {
render(h) {
return h('a', 'router-link')
}
})
此时项目已经不报错,页面也可以正常渲染了
修改router-link
渲染内容:
Vue.component('router-link', {
props: {
to: {
type: String,
require: true
}
},
render(h) {
// 用户使用方式为 <router-link to="/about">内容</router-link>
// <a href="#/about">内容</a>
// 通过 this.$slots.default 来获取内容
return h('a',{ attrs: {href: '#' + this.to}}, this.$slots.default)
}
})
此时 router-link 中的内容也正常了。
现在写VueRouter
类:
class VueRouter {
// Vue要在这里使用
constructor(options) {
// 1.处理选项
this.$options = options
// 初始化url地址
this.current = '/'
// 2.监控url变化
// 因为后边的回调函数是指向了window,所以要bind一下
window.addEventListener("hashchange", this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1)
}
}
思考一下下方 router-view
组件的工作方式?
Vue.component('router-view', {
// 注意:这里不能写template,因为目前的vue版本是运行时(runtime-onle)版本,不带编译器的
render(h) {
return h('div', 'router-view')
}
})
它是一个内容的容器,因此容器的标签不能写死为 div
,它需要把用户配置的映射表中的 **component**
取出来,再渲染出来
Vue.component('router-view', {
// 注意:这里不能写template,因为目前的vue版本是运行时(runtime-onle)版本,不带编译器的
render(h) {
let Component = null
// 获取 current
const route = this.$router.$options.routes.find(route => route.path === this.$router.current)
if (route) {
Component = route.component
}
return h(Component)
}
})
解析:route
变量:在 mixin
中已经将 $router
保存到 Vue
的实例中(Vue.prototype.$router = this.$options.router
),因此,this.$router.$options.routes
获取的是 new VueRouter
中的 options
中的 routes
,即如下代码的 routes
:
// lrouter/index.js
const router = new VueRouter({
routes
})
此时可以看到页面可以正常输出了:
但是切换 Home 和 About 页面并不会重新渲染,因为 current
并不是响应式数据,需要修改 current
为响应式数据,我们根据 Vue 提供的一个 API(Vue.util.defineReactive
) 来创建 current
为响应式数据:
class VueRouter {
// Vue要在这里使用
constructor(options) {
// 1.处理选项
this.$options = options
// 需要响应式current
const initial = window.location.hash.slice(1) || '/'
Vue.util.defineReactive(this, 'current', initial)
// 2.监控url变化
// 因为后边的回调函数是指向了window,所以要bind一下
window.addEventListener("hashchange", this.onHashChange.bind(this))
}
onHashChange() {
this.current = window.location.hash.slice(1)
}
}
此时页面点击链接后可以显示对应组件内容了。但是如果出现嵌套路由,则无法正常渲染,因为没有处理嵌套路由的逻辑。。
回答:
问题1:Vue.use(Router)
的时候内部会做什么事情?
- 利用全局混入将
$router
挂载到Vue
实例上 - 注册
router-view
和router-link
全局组件
问题3:router-link
为什么可以直接使用,而不需要注入?
- 因为在
Vue.use(VueRouter)
时注册了router-link
全局组件