先来几个问题吧,
基础全局 options 是什么?
基础 options 就是:components、directives、filters 三兄弟,这三兄弟在初始化全局 API 的时候就设置在 Vue.options 上。所以这三个是最先存在全局 options。
那options里面还有什么呢?
挂载点:el 数据:data 模板:template 方法:methods生命周期[...] 监听器watch...
更多移步 官方文档
如果给选项配置对象传入一个自定义配置,我们如何在实例上获取它们?
我们可以通过实例方法获取
vm.$options[自定义的key] 获取
当我们的项目越来越大,可能会发现在相似的组件里一遍又一遍的在复制粘贴相同的代码段(data,method,watcher等)。当然,也可以把相似的组件写成一个组件,然后用props来定制它,但是使用太多的props很容易导致混乱。
不过,我们还有一种解决方案,那就是Mixin(混入)。
正文
一、Mixin介绍
Mixin是什么
Mixin 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能,Mixin对象能够使用组件的任何属性(data,mounted,created,update等),而且当组件使用Mixin时,这个Mixin的所有信息也会混合到这个组件里,这个组件就能够访问到所有Mixin的属性就像声明在自己对象中一样
这时,可以通过Vue的mixin功能将相同或者相似的代码提出来。如一些逻辑相同弹框,将其是否展示的逻辑抽离出来。或者当我们需要全局去注入一些methods, filter或者hooks时我们就可以使用mixin来做。
特性:全局和局部 对每一个选项配置的混入
混入的作用:
- 将混入配置项和组件配置项进行合并
混入类型:全局混入,局部混入
- 全局混入指的是对所有组件的配置进行合并
//写在vue实例创建之前 Vue.mixin()
- 局部混入指的是对当前组件的配置进行合并
-
//写在vue实例options内 mixins:[{}]
- 多个混入对象同时执行,不会覆盖
什么场景使用?
当你的逻辑需要多个组件复用,但是逻辑中需要使用多个data methods 生命周期的时候就需要混入
混入的特性?
- data/computed/methods/filter/components配置项不同名属性合并,同名属性以组件自己的为准 生命周期函数是将所有的逻辑全部合并执行
mixin的规则
定义了一个全局的混入配置
vue.mixin( {
data() {
return { msg: 'hello,mixin!' };},
});
导入全局mixin并渲染到页面
<template>
<div class="App">
<h1> App{{ msg }}</h1>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
实现router-view和vue-link
基于此我们终于可以步入今天的正题啦,简单实现一下页面,组件切换的插件啦~~~ 其实就是router-view和router-link的底层逻辑
那么,在实现这个功能是我们将会用到哪些知识点呢?
- 动态组件 components
- Vue.mixin
- Vue.use
- vue.observable
- vue.components
- Location.hash
- 路由系统 ---> (hash值对应组件渲染)
- 通过hash值变化动态渲染组件
- class语法
部分知识点将以注释的形式列出哦!!!
先贴出demo目录树
❀❀开始❀❀ --->
- 在项目根目录下新建
vue-router文件夹,里面新建index.js - 在根目录main.js里面引入该文件
index.js,并使用vue.use关于use的使用点这里vue.use 将其注册为全局组件,我们还应该实例化一个路由对象,该对象有其配置项,配置项是hash值与自己写的组件之间的对应关系,最后我们将其实例化对象注册到全局根vue实例的options里,前面说,我们的自定义配置项会保存在vm.options里面,可以通过vm.options.[自定义的key] 访问到 那么我们在根目录[main.js]下的任务就写完了,代码如下:
import Vue from 'vue/dist/vue';
import App from '@/App.vue';
//1.全局引入路由文件
import VueRouter from './vue-router';
// 2.通过全局方法 Vue.use() 使用插件,并且 VueRouter如果是对象就必须存在一个静态方法 install,它的参数就是我们的vue
Vue.use(VueRouter);
// 3.实例化路由对象--传hash值与页面组件对应关系
const router = new VueRouter({
'/': { template: '<h1>/页面</h1>' },
'/a': { template: '<h1>A页面</h1>' },
'/b': { template: '<h1>B页面</h1>' },
'/c': { template: '<h1>C页面</h1>' },
});
const vm = new Vue({
el: '#app',
// 4.将router实例注入到vm.$options中以便后续操作
router,
template: '<App />',
components: { App },
});
- 接着我们返回到刚刚index.js中,在里面导出一个定义路由的构造函数的类 [该类利用hash值的变化动态渲染组件] 并为其注册一个静态方法
install,此时install里的参数就是该vue实例,我们后续还会用到很多次,避免每次都导入,那么我们可以将其保存下来,let _Vue = null将全局Vue实例挂载到该类,_vue = Vue以便以后每一个组件路由都有调用Vue实例的权力 - 这里我们就会在install方法里面用到我们的
mixin,将该vue实例全局混入,那么它成功后会对后续每一个组件都都执行该方法,其中我们知道this在vue中,如果不限制组件,将指向当前组件,而我们这里的this在根实例执行,所以this指向Vue,接着beforeCreate函数, - 然而当前的mixin是添加在根实例中的全局混入,我们又希望
beforeCreate函数只在根实例中执行,所以,我们要判断!this.$parent,当一个组件没有父组件时,它就一定是根组件,这时,实例化的options中的东西就又可以在构造函数中拿到。 - 定义一个初始化方法
init,并在beforeCreate中调用该方法 代码如下:
let _Vue = null; //声明储存全局Vue实例的变量
// 定义路由的构造函数的类---利用hash值的变化动态渲染组件
export default class VueRouter {
// 7.构造器接收实例化的路由对象
constructor(options) {
this.name = 'vue-router'
}
// 8. 定义init方法 执行初始化
init() {
console.log('init执行了');
}
// 3. 必须存在一个静态方法 install
static install(Vue) {
// 4.将全局Vue实例挂载到该类,以便以后每一个组件路由都有调用Vue实例的权力
_Vue = Vue;
// 5.全局混入
Vue.mixin({
beforeCreate() {
//此处this指向Vue实例
// console.log('--------------', this);
// 6.确保只在根实例中执行
if (!this.$parent) {
// console.log(this.$options.router);
// 9. 调用init方法
this.$options.router.init()
}
},
});
}
}
-
因为我们在
main.js中实例化该类时传了路由关系的参数,所以我们要在当前类中接受这组参数options,那么当前默认渲染根组件,此时,细心的你如果打印location.hash,就会发现它是一个空值,因为我们的页面并没有发生变化,那么对应的hash值也将不会发生变化。 -
如果想要我们的页面发生变化,我们就需要获取到
hash值,并且注册hashChange事件,并且,将当前渲染页面的hash值保管起来,当hash值变化后,将变化后的hash值赋值给保存的当前路径,修改hash,页面同步更改, -
修改后的地址变化 -
此时要想同步改变页面,我们还需要写一个动态组件的方法
initComponents并在组件中注册一个叫做router-view的vue.components, 代码如下:
let _Vue = null; //声明储存全局Vue实例的变量
// 定义路由的构造函数的类---利用hash值的变化动态渲染组件
export default class VueRouter {
// 7.构造器接收实例化的路由对象
constructor(options) {
this.options = options;
// 12.将当前页面hash保存起来,Vue.observable,让一个对象变成响应式数据。Vue 内部会用它来处理 data 函数返回的对象
this.current = _Vue.observable({ path: '' });
}
// 8. 定义init方法 执行初始化
init() {
// 15.初始化组件
this.initComponents();
// 10.调用初始化事件
this.initEvent();
}
// 9.定义一个 window.onhashchange 监听函数,
initEvent() {
// 11.定义hashchange事件
window.onhashchange = () => {
console.log(window.location.hash);
// 13.将变化后的hash赋值给当前路径中并去除#
this.current.path = window.location.hash.substring(1);
};
// 14.由默认的页面中的hash变化
window.location.hash = '/';
}
// 16. 注册两个路由实例
initComponents() {
console.log('---------', this);
// 18.保存当前this
const _this = this; //this指向VueRouter实例
// 17.注册渲染当前页面组件
_Vue.component('router-view', {
template: '<component :is="currentView" />',
computed: {
currentView() {
// 传过来的路径[/]
return _this.options[_this.current.path];
},
},
});
}
// 3. 必须存在一个静态方法 install
static install(Vue) {
// 4.将全局Vue实例挂载到该类,以便以后每一个组件路由都有调用Vue实例的权力
_Vue = Vue;
// 5.全局混入
Vue.mixin({
beforeCreate() {
//此处this指向Vue实例
// console.log('--------------', this);
// 6.确保只在根实例中执行
if (!this.$parent) {
// 将router的实例挂载到vue的原型上,每个vue实例都可以访问到
_Vue.prototype.$router = this.$options.router;
// console.log('++++', this.$options.router); //此处是挂载到Vue实例上的路由实例
// 8.调用路由实例上的init方法
this.$options.router.init();
}
},
});
}
}
- 此时我们就注册好了一个全局组件,我们可以在任何地方直接使用它,例如我们在
App.vue,
<template>
<div class="App">
<router-view></router-view>
</div>
</template>
<script>
export default {
};
</script>
<style>
</style>
示图:
12. 当然这给只能在地址栏输入渲染新页面,显然不能满足我们,那我们还需要一个类似于
a标签点击跳转的功能,
- 我们就还需要注册一个
router-link的全局组件
//14.注册点击跳转的组件
_Vue.component('router-link', {
template: '<a :href="`#${to}`"> <slot /></a>',
props: {
to: {
type: String,
required: true,
},
},
});
那么在页面中这么用
<template>
<div class="App">
<router-view></router-view>
<router-link to="/c">去c页面</router-link>
<router-link to="/a">去A页面</router-link>
</div>
</template>
- 那我希望用普通的
button也可以跳转呢?其实都写到这了,也不难,只需要在刚刚我们定义的类里添加自定义的push方法,让当前路径等于点击后传过来的路径即可😀 - 将router的实例挂载到vue的原型上,每个vue实例都可以访问到
App的button按钮点击跳转
<template>
<div class="App">
<router-view></router-view>
<router-link to="/c">去c页面</router-link>
<router-link to="/a">去A页面</router-link>
<button @click="toB">去B页面</button>
</div>
</template>
<script>
import Test from './components/test.vue';
export default {
methods: {
toB() {
this.$router.push('/b');
},
},
};
</script>
<style>
</style>
push方法的逻辑---15
// 15.让当前路径==传过来的路径
push(path) {
this.current.path = path;
}
将router的属性挂载到所有vue原型上---16
// 5.全局混入
Vue.mixin({
beforeCreate() {
//此处this指向Vue实例
// console.log('--------------', this);
// 6.确保只在根实例中执行
if (!this.$parent) {
// 16.将router的实例挂载到vue的原型上,每个vue实例都可以访问到
_Vue.prototype.$router = this.$options.router;
// 8.调用路由实例上的init方法
this.$options.router.init();
}
},
});
到这里,我们的router-view与router-link的底层逻辑就实现完了,想要了解更多,请移步官方文档😀😀😀
结束❀❀
每天的学习都是痛苦,煎熬与恍然一悟,惊喜的过程,虽然很难,但坚持下去,结局总不会太差!!! 加油,林寒!!