vue进阶---vue-router的实现

908 阅读1分钟

一、前言

这次分享一下我了解到的关于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();
        }
      },
    });
  }
}