1. 准备项目环境
一、初始化项目
使用 vue脚手架 创建项目
vue create my-vuerouter
等待项目初始化完成,进行项目的运行
npm run serve
二、保留有用的代码
Home.vue
<template>
<div class="home">
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'Home',
components: {
HelloWorld
}
}
</script>
HelloWorld.vue
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
2. 实现vuerouter
一、创建文件
在 src 下创建vuerouter/index.js
二、编写代码
1. 实现 install 静态方法
install 方法需要实现以下功能:
a. 判断当前插件是否已经安装
b. 把 Vue 的构造函数记录在全局
c. 把创建 Vue 的实例传入的 router 对象注入到 Vue 实例
判断当前插件是否已经安装
//1 判断当前插件是否被安装
if(VueRouter.install.installed){
return;
}
VueRouter.install.installed = true
把 Vue 的构造函数记录在全局
let _Vue = null // 定义一个全局变量来记录Vue构造函数
//2 把Vue的构造函数记录在全局
_Vue = Vue
把创建 Vue 的实例传入的 router 对象注入到 Vue 实例
//3 把创建Vue的实例传入的router对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate(){
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if(this.$options.router){
_Vue.prototype.$router = this.$options.router
}
}
})
完整的 install 静态方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
2. 实现 constructor 构造函数
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
}
3. 实现 createRouteMap 方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
}
4. 实现 initComponents 方法中的 router-link方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
template: '<a :href="to"></a>'
})
}
}
5. 实现 init 方法
测试 createRouteMap 和 initComponent 方法,需要在创建 Vue 的实例时,执行这两个方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
init () {
createRouteMap()
initComponent(_Vue)
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'
})
}
}
修改 router/index.js
import Vue from 'vue'
// import VueRouter from 'vue-router'
import VueRouter from '../vuerouter'
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({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
测试🌰
npm run server
打开网页会看不到效果,在控制台中有两个报错
[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
vue 的构建版本
**运行时版:**不支持 template 模版,需要打包的实收提前编译
**完整版:**包含运行时和编译器,体积比运行时版大 10k 左右,程序运行的时候把模板转换成 render 函数
原因: vuecli 创建时使用 运行时版本
解决方案1:使用完整版的 Vue 参考资料
vue.config.js
module.exports = {
runtimeCompiler: true,
// 取消 eslint 校验
lintOnSave: false,
devServer: {
overlay: {
warning: false,
errors: false
}
},
}
再次执行 npm run serve,就能正常显示的两个链接了
解决方案2:initComponents 方法中实现 render 函数
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
render (h) {
return h("a", {
attrs: {
href: this.to
},
}, [this.$slots.default])
},
// template: '<a :href="to"><slot></slot></a>'
})
}
}
6. initComponents 方法中实现 router-view 方法
增加 router-view 方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
render (h) {
return h("a", {
attrs: {
href: this.to
},
}, [this.$slots.default])
},
// template: '<a :href="to"><slot></slot></a>'
})
const self = this
Vue.component("router-view", {
// 创建虚拟 dom
render (h) {
// 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
// self.data.current
const component = self.routeMap[self.data.current]
return h(component) // 返回虚拟 dom
}
})
}
}
这时候测试发现,点解链接时会发送 http 请求,为了阻止发送 http 请求,需要在组件中注册点击事件来阻止发送请求
完善router-link方法
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
render (h) {
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
// template: '<a :href="to"><slot></slot></a>'
methods: {
clickhander (e) {
// 改变地址栏
history.pushState({}, "", this.to)
// 加载路径对应组件
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component("router-view", {
// 创建虚拟 dom
render (h) {
// 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
// self.data.current
const component = self.routeMap[self.data.current]
return h(component) // 返回虚拟 dom
}
})
}
}
7. 实现 initEvent 方法
解决 history 发生变化的时候,渲染对应的组件
let _Vue = null // 定义一个全局变量来记录Vue构造函数
export default class VueRouter {
static install (Vue) {
// 1. 判断当前插件是否已经安装
if (VueRouter.install.installed) {
return;
}
VueRouter.install.installed = true
// 2. 把 Vue 的构造函数记录在全局
_Vue = Vue
// 3. 把创建Vue的实例传入的 router 对象注入到Vue实例
// _Vue.prototype.$router = this.$options.router
// 混入
_Vue.mixin({
beforeCreate () {
// 判断是否为组件,是就不执行;否则为实例,需要执行。只用 vue 中才有 $options.router 的属性,而在 组件中没有的
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor(options) {
this.options = options
this.routeMap = {}
// observable
this.data = _Vue.observable({
current: "/"
})
}
init () {
this.createRouteMap()
this.initComponent(_Vue)
this.initEvent()
}
createRouteMap () {
// 遍历所有的路由规则 把路由规则解析成键值对的形式存储到 routeMap 中
this.options.routes.forEach(route => {
this.routeMap[route.path] = route.component
});
}
initComponent (Vue) {
Vue.component("router-link", {
props: {
to: String
},
render (h) {
return h("a", {
attrs: {
href: this.to
},
on: {
click: this.clickhander
}
}, [this.$slots.default])
},
// template: '<a :href="to"><slot></slot></a>'
methods: {
clickhander (e) {
// 改变地址栏
history.pushState({}, "", this.to)
// 加载路径对应组件
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component("router-view", {
// 创建虚拟 dom
render (h) {
// 先找到当前路由地址,然后通过routerMap中找到当前路由地址对应的组件,然后把该组件转换成虚拟 dom返回
// self.data.current
const component = self.routeMap[self.data.current]
return h(component) // 返回虚拟 dom
}
})
}
initEvent () {
// this 是 vuerouter 对象
window.addEventListener("popstate", () => {
this.data.current = window.location.pathname
})
}
}
测试点解浏览器的返回按钮会渲染上一个路径的组件