美文网首页
[学习vue]全家桶的原理和实现

[学习vue]全家桶的原理和实现

作者: 巧克力_404 | 来源:发表于2020-06-28 16:09 被阅读0次

    在正式进入之前先抛出几个问题:
    1 vue-router能不能放在react中使用?
    2 use vueRouter的时候发生了什么?
    3 为什么要把router作为一个选项放在new vue 中?
    4 为什么router-link router-view 不需要注册,就可以直接使用?
    答案就在文章中~~

    vue-router

    Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。主要解决的问题就单页面应用的导航问题。

    router的任务分析

    • 解析routers选项
    • 监控url变化
      html5 history api /login
      hash xx.html#login
    //vue-router.js
    //声明插件:vue插件需求实现一个install静态方法
    let Vue; //保存vue构造函数引用 目的是不用吧vue打包进去
    class KVueRouter{
    }
    //参数是vue构造函数
    KVueRouter.install = function(_vue){
        Vue = _Vue;
        //实现一个混入
        Vue.mixin({
              beforeCreate(){
                   //获取KVueRouter实例并挂载到Vue.prototype
                   if(this.$options.router){
                      // 在跟组件beforeCreate时执行一次且只会执行一次
                       Vue.prototype.$router = this.$options.router;
                   }
             }
        })
    }
    

    这时候有一个疑问,为什么要写把router挂载的配置写在混入里,而不是直接写在install方法呢?
    这是因为和实例的关系很大,我们先执行了Vue.use的方法,其实是执行了插件的install的方法,但是这时候,实例还不存在呢,那怎么办呢,我们只好退而求其次,把代码延后执行,延后到当beforeCreate的时候,才执行。
    接下来我们需要注册两个全局组件 router-view 和 router-link

    ...
    Vue.component('router-link',{})
    Vue.component('router-view',{})
    

    接下来来完成核心任务

    class KVueRouter{
             //解析routes
             //监听事件
             //声明组件
        constructor(options){
              this.$options = options;
              this.routeMap = {};  // {'/index': {component: Index,...}}
              //当前url需要响应式的
              this.app = new Vue({
                   data : { current : '/'}
              })
        }
    
        //初始化
       init(){
          //监听事件
           this.bindEvents();
          //解析routes
           this.createRouteMap();
           //声明组件
           this.initComponent();
       }
       bindEvents(){
            window.addEventListener('hashchange', this.onHashchange.bind(this))
       }
       onHashchange(){
           this.app.current = window.location.hash.slice(1) || '/'
       }
       createRouteMap(){
          //遍历用户配置路由数组
          this.$options.routes.forEach(route => {
                this.routeMap[route.path] = route;
          })
       }
      initComponent(){
          //转换目标: <a href = '/'>xx</a>
          // <router-link to = '/'>
         Vue.component('router-link', {
               props: {
                     to: String
               },
               render(h){
                     // h(tag, data,children)
                    return h( 'a' , {
                          attrs:{href : '#' + this.to}
                    }, [ this.$slots.default ]) // 这里还可以放其他的具名插槽 甚至作用域插槽 
                   // 也可以使用jsx
               }
         })
      }
    }
    

    最后执行一下install方法,把KVueRouter导出一下

    export default KVueRouter
    

    这时候可以引入写好的文件,来调试一下了

    //router.js
    import Vue from 'vue'
    // import Router from 'vue-router'
    import Router from './kvue-router'
    import Home from './views/Home.vue'
    
    // 1.应用插件:做了什么?
    Vue.use(Router) // use执行了插件install()
    
    // 2.创建Router实例
    export default new Router({
      mode: 'history',
      base: process.env.BASE_URL,
      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')
        }
      ]
    })
    

    在main.js中挂载router

    //main.js
    import router from './router'
    new Vue({
      router, // 配置router实例
      render: h => h(App),
    }).$mount("#app");
    
    //app.vue
    <div id="nav">
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link>
        </div>
        <router-view></router-view>
    

    这时候我们可以实现路由跳转了,但是并不能真正的加载对应的组件,就差渲染内容组件了
    router-view 这个组件负责实现渲染组件工作,我们拿出我们要渲染的component

       // 获取path对应的Component将它渲染出来
        Vue.component("router-view", {
            render: (h) => {
                const Component = this.routeMap[this.app.current].component;
                return h(Component)
            }
        })
    

    另外vue也像我们暴露了一个可以定义一个响应式数据的方法

    const initial = window.location.hash.slice(1) || ‘/’
    Vue.util.defineReactive(this,’current’,initial)
    

    接下来,我们来总结一下
    关于实现路由,我们要做的第一件事是实现一个插件install(),这个install方法,会把vue的构造函数传进来,拿到vue的构造函数之后,我们就可以做很多事情,接下来我们另一个要实现的kvuerouter里,监听事件,事件发生变化以后,我们要做的事情把this.app的current的改成新的hash,但是为什么修改完新的hash之后,会在router-view把对应的组件渲染出来呢,原因是只要render函数里面用到了某个响应式的数据,这个数据发生变化了,我们的组件就会重新执行render,这就是典型的依赖收集,意思就是说render函数里只要用的data里的东西,就会产生依赖,编辑器在执行render函数的时候,会先执行依赖收集的过程,先把依赖全部找到,vue里的current只要改变,和它相关的组件都会发生改变,也就会导致router-view的component的重新执行,界面就渲染了。
    该示例没有解决嵌套路由的问题,我们可以参考一下官方的文档。

    vuex数据管理

    vuex是一个专门为vue.js应用开发的状态管理模式,集中式存储管理应用所有组件的状态。它是一个单项数据流的设计思想,它为了让数据可控,数据可追踪,设计出这样一个单项数据流, 我们在实践的时候,也要避免同时被父子组件操作的情况,维持这样一个单向的关系,怎么去维系呢,我们把一些通用的全局的数据,把他抽象到一个store里去保管,只能用不能改,如果想改数据,只能commit一个mutaions,或者dispath一个actions,让actions去commit一个mutaions,而且在vuex 里面必须实现一个数据的响应式,实现的方式也是利用了vuex的构造初始化的时候做了响应式。


    vuex

    核心概念

    state状态,数据

    • mutations更改状态的函数
    • actions异步操作
    • store包含以上概念的容器

    状态和状态的变更

    state保存数据状态,mutations用于修改状态,store.js

    export default new Vuex.Store({
          state: {count : 0},
          mutations:{
                increment(state){
                     state.count += 1;
                }
          }
    })
    

    vuex的任务分析

    • 实现插件: $store挂载
    • 实现store: 解析vuex配置,持有state,实现dispatch,commit,getters
    • 借助vue实现数据响应式
    //kvuex.js
    let Vue;
    class Store {
      // 持有state,并使其响应化
      // 实现commit和dispatch两个方法
      constructor(options) {
        //   数据响应式
        // this.state是Vue实例,访问this.state.count
        this.state = new Vue({ data: options.state });
    
        this.mutations = options.mutations;
        this.actions = options.actions;
        this.getters = options.getters;
    
        // bind this
        this.commit = this.commit.bind(this);
        this.dispatch = this.dispatch.bind(this);
        this.getters = this.getters.bind(this);
      }
    
      //   实现commit:可以修改state中的数据
      commit(type, arg) {
        this.mutations[type](this.state, arg);
      }
    
      dispatch(type, arg) {
        return this.actions[type](this, arg);
      }
    
     getters(getters){
         //遍历getters选项,为this.getters定义property
         //属性名就是选项中的key ,只需定义get函数保证只读性
        Object.keys(getters).forEach(key =>{
            Object.defineProperty(this.getters, key, {
               get : () =>{
                   return getters[key](this.state)
               }
           })
       })
     }
    // 声明插件install
    // _Vue是形参:Vue构造函数,use会把它传进来
    function install(_Vue) {
      Vue = _Vue;
    
      Vue.mixin({
        beforeCreate() {
          // this指的是组件实例
          if (this.$options.store) {
            Vue.prototype.$store = this.$options.store;
          }
        },
      });
    }
    
    // 导出Vuex
    export default { Store, install };
    
    

    vuex和vuerouter的实现思想大致相同,以上在不考虑代码健壮性的前提下,来实现核心思想的。

    相关文章

      网友评论

          本文标题:[学习vue]全家桶的原理和实现

          本文链接:https://www.haomeiwen.com/subject/hfeuxktx.html