一、前言
这次分享一下我了解到的关于vue里面路由的部分知识,但是在正式分享之前还需要了解一些其他使用到的知识点!
二、$options
问题: 在vm实例的选项配置中传入一个自定义配置,那么怎样在vm实例上面去获取它。
// 举例:怎样才能拿到下面自己定义的 abc123 的这个自定义选项配置
const vm = {
abc123: { a:1 },
el: "#app",
template: ...,
conponents: { ... }
}
console.log(vm) // 在通过打印vm实例之后,会在选项$options中找到abc123
结论: 在vm实例上的自定义选项配置(没有出现在官方文档上面的配置项)都可以在vm.$options[自定义的选项配置名]来获取
三、mixin
分类: 全局混入、局部混入
使用: 对于混入这个配置项,它也分为全局混入和局部混入,它和前面的那个组件的配置项一样,全局混入使用Vue.mixin()的静态方法, 局部混入使用mixins的配置项,配置项后面跟一个数组,数组中的每一个元素是一个单独的配置项对象
作用: 它的作用跟混合类似,它会将混入的配置项与所有组件的配置项相融合,如果文件的配置选项中存在相同的key值,那么就会以文件本身定义的key为准,否则以我所混入的配置项的key为准。
// 在main.js全局混入
Vue.mixin({
data(){
return {
msg: "hello,mixin"
}
}
})
// 在test.vue此文件中我并未定义过msg这个数据
<template>
// 虽然我没有定义过msg这个数据,但是因为我在main.JS文件中进行过全局
// 的混入,表示在test.vue文件里面也会存在我所定义的这个data的配置项
// 当然,也就存在msg这条数据了,所以在此处使用并不会报错。
<span>{{ msg }}</span> // hello,mixin
</template>
配置对象中的属性名重名:
// main.js
Vue.mixin({
data(){
return {
msg: "hello,mixin"
}
}
})
// test.vue
<template>
// 全局混入和文件都存在相同的配置的时候,会以文件本身的为准,所以这里会
// 显示hello,test
<span>{{msg}}</span> // hello,test
</template>
<script>
export default {
data(){
return {
msg: "hello,test"
}
}
}
</script>
注意: 所以除了官方文档中的配置项可以重名以外,其它的自定义的配置项最好不要出现重名的情况`
混入与生命周期:
// 在test.vue文件中局部混入
<script>
export default {
data(){
return {
msg: "hello,test"
}
},
created(){
console.log(0)
},
mixins: [
{
created(){
console.log(1)
},
},
{
created(){
console.log(2)
},
},
]
// 在控制台打印的时候会发现,会打印出1,2,0,也就是对于生命周期函数,
// 它会先把混入的生命周期函数执行掉,然后在执行自己文件本身自带的,就算
// 重名的情况出现也是如此。
}
</script>
使用场景:如果存在一段逻辑需要使用多次,并且这段逻辑里面使用了data、computed、等选项配置时,就可以使用混入将其代替了。
// 举例:假设我需要在每一个组件中获取两个随机数
// 全局混入
Vue.mixin({
data(){
x: 0,
y: 0
},
created(){
this.x = Math.random(),
this.y = Math.random()
}
})
优化写法:当然,对于混入,可以使用一个单独的文件夹进行管理,假如称其为mixin文件,然后文件夹里面的每一个文件除了写混入的配置选项之外,也可以添加一个install的方法,写这个方法后就可以跟使用组件一样使用use的方法进行注册了。
// 举例:将上面的文件我将其改写成一个单独的名为random的文件
// mixin/randow.js
export default {
data(){
x: 0,
y: 0
},
created(){
this.x = Math.random(),
this.y = Math.random()
}
install(v){
v.mixin(this) // 参数v表示vue实例,this表示当前的配置对象
// 注册混入是mixin,不是component(组件)
// =>
// 调用这个install的方法就实现了全局混入当前
// 的这个配置项
}
}
// main.js
// 在main.js中,使用use的方法时,它会调用组建内部定义的install的方法
Vue.use(mixin/randow.js)
四、Vue.observable
概念:当对象里面的数据发生改变,会导致引用该部分数据的ui视图层发生变化,这样的对象称为响应式对象。
注意:vue中并不是所有的对象都是属于响应式对象
// 举例:在不借助data配置项的情况下来渲染数据
// 假设在source文件夹下面的index.js文件内定义了一下内容
const state = {
a: 123,
b: 456
};// 定义数据
export const changeA = (val) => {
state.a = val
}; // 改变A值
export const changeB = (val) => {
state.b = val
};// 改变B值
export default state; // 定义数据
// main.js中
<template>
// 因为上面定义的对象不是一个响应式的,那么虽然点击事件触发改变了存放在
// state里面的值,但是并不会将改变后的值渲染到页面上,这也就证明了vue中
// 并不是所有的对象都是属于响应式对象,如果想要这个对象变成响应式的话,
// 可以使用 Vue.observable(一个对象)的静态方法将其改变。
<button @click="changeA(321)">{{statu.a}}</button>
<button @click="changeA(432)">{{statu.b}}</button>
</template>
<script>
import _state from "../source" // 引入数据
import { changeA, changeB } from "../source" // 引入方法
export default {
computed: {
state(){
return _state
}
},
methods: {
changeA,
changeB
}
}
</script>
五、vue-router的仿写
使用:
// main.js
// 1.导入封装的vue-router组件
import VueRouter from "./vue-router"
// 2.将导入的组件use一下
Vue.use(VueRouter)
// 3.实例化一个路由对象
// 其中:实例的参数为hash值和组件的对应关系
const router = new VueRouter({
// 举例:当hash值为a,渲染A页面....当hash值为c,渲染C页面
a: A,
b: B,
c: C
})
// 4.将路由对象传递给根组件
const vm = new Vue({
el: "#app",
router,
})
封装vue-router组件:
// 2.因为在install方法里面会拿到Vue的实例,所以可以将其保存起来,
// 避免后面在该组件中需要用到实例的时候导致的焦头烂额
let _Vue = null;
export default class VueRouter {
constructor(options) {
// 7.在实例化路由对象的时候传递了参数,这里将其接住
this.options = options;
// 9.将当前hash值渲染的路径保存起来。
this.current = _Vue.observable({ path: '' });
// 16.这个对象需要变成响应式对象,否则hash值变化的时候页面并不会切换
}
// 5.init用于触发组件内部的一些方法
init() {
this.initComponents();
this.initEvent();
}
// 8.根据上面初始化的路由实例看,路由的切换是根据hash值的改变而改变的,
// 那么就需要去监听hash值得变化了,在动态组件哪里说到过,监听hash值
// 的变化可以通过onhashchange的事件来监听,然后监听的话需要全局监听,
// 所以这里将事件绑定给window,便于初始化,将注册事件封装为一个函数
// 方便执行。
initEvent() {
// 17.这里的this指向window,很明显,这里的this需要指向vue实例,所以
// 可以使用箭头函数或者前面定义的_this
window.onhashchange = () => {
// 10.当hash值变化之后,渲染的路径也会变化,为了让hash值渲染的路
// 径为最新的状态,那么就将每次hash值变化之后的路径赋值给渲染
// 的当前路径,但是hash值是以#开头的字符串,所以需要将其去掉
this.current.path = window.location.hash.substring(1);
};
// 11.刚开始的hash值是空的,不存在hash值,但是又需要它默认存在一个
// hash值,将其值变为"/",这样做以后,页面一初始化的时候,它就会
// 从没有hash值变化为hash值为"/",以此触发事件,将其赋值,这样
// 页面默认的hash值就是 #/ 了
window.location.hash = '/';
}
// 12.然后就该渲染组件了,那么首先需要找到我该渲染那个组件,因为组件的
// 切换是由于hash值的切换所产生的,在上面这个值被this.current.path
// 存储了起来,这样我就需要找到这个路径对应的是哪一个组件就可以了,
// 在实例化vue-router的时候,传过来了一个对象,这个对象的内容是hash值
// 与组件路径的对应关系,这个对象在上面被this.options接住了,这样就可
// 以使用this.options[this.current.path]拿到了我应该渲染的组件,根据
// 前面的动态组件可以得到这里可以根据动态组件来进行渲染,然后这里注册
// 的组件的类型应该是全局组件,如果是局部组件的话,在这个组件的外面就
// 不能够使用了。
initComponents() {
// 14.将指向vue的this保存起来传递给15用
const _this = this;
_Vue.component('router-view', {
template: '<component :is="currentView" />',
computed: {
// 13.将返回的具体的组件路径传递给动态组件并进行绑定(疑惑)
currentView() {
// 15.这个里面的this并不会指向vue,而是会指向注册的组件
// router-view,所以需要使用外界指向vue的this
return _this.options[_this.current.path];
},
},
});
// 16.封装一个router-link的组件,跟a标签的作用一样,用于点击跳转,
// 但是这里的herf只需要改变hash值,所以需要在路径的前面加上一个
// #起到不跳转页面的作用,后面的 插槽用来接住其传递的内容
_Vue.component('router-link', {
template: '<a :href="`#${to}`"><slot/></a>',
props: {
to: {
type: String,
required: true,
},
},
});
}
// 18.如果外界需要使用其他的方式跳转页面,只需要将跳转的路径传递
// 作为参数传递进来并且将this.current.path的值修改掉就可以
push(path) {
this.current.path = path;
}
// 1.因为在使用的时候调用了一个use的方法,那么其vue-router组件的内部会
// 存在一个install的静态方法
static install(Vue) {
// 3.将获取到的vue实例存储为全局变量
_Vue = Vue;
Vue.mixin({
beforeCreate() {
// 4.上面我是把实例化的路由对象传递给了根实例,这就表示我只能在
// 根实例中获取到路由对象,其它实例都获取不到这个路由对象,那
// 么这里需要做一点限制,将其限制在根组件中,之前我写过,如果
// 一个组件它没有父组件那就表示它是一个根组件,父组件用$parent
// 获取
if (!this.$parent) {
// 17.将实例化的router实例绑定给Vue的原型上面,让其都可以访问到
_Vue.prototype.$router = this.$options.router;
// 6.因为init方法是实例方法,只有router实例才能触发,但是在根组件
// 中,this.$options.router可以获取到这个router实例,自然也可
// 以触发这个方法了
this.$options.router.init();
}
},
});
}
}