vue2全家桶原理

vuex原理解析

vuex的设计理念是:集中状态管理,可预测状态变化

插件

在使用vuex时,首先需要Vue.use(Vuex),也就是安装vuex插件。

  • 怎么实现一个插件?

    很简单,对外暴露一个install()方法。

  • 这个插件的主要功能是什么?

    挂载$store,因为Vue.use()先执行,此时还没有创建new Vue()实例,所以使用mixin混入延迟挂载。

function install(_Vue) {
//Vue.use() 可以接收到Vue实例
  Vue = _Vue

  Vue.mixin({
    beforeCreate() {
      // 因为使用混入,钩子在每个组件创建实例时都会调用
      // 根实例才有该选项
      if (this.$options.store) {
        //挂载vue原型方便其他组件调用$store
        Vue.prototype.$store = this.$options.store
      }
    }
  })
}
复制代码

state 响应式状态管理

  • 实现数据响应式的几种方式?
  1. Object.defineProperty()
  2. Vue.util.defineReactive()
  3. Vue.observable()
  4. new Vue({data(){}})

这里用的是第四种

new Vue({
  data: {
    //$$state为什么用$$不代理,外部不能访问
    $$state:options.state
  }
})
复制代码

state封装

//只读state,可以获取数据
get state() {
    return this._vm._data.$$state
}

set state(v) {
    console.error('please use replaceState to reset state');
}
复制代码

实现commit() dispatch()方法

传入方法名和参数,去mutationsactions匹配

// 保存mutaions和actions选项
    this._mutations = options.mutations;
    this._actions = options.actions;
    
 commit(type, payload) {
    const entry = this._mutations[type]
    if (!entry) {
      console.error('unkown mutation type');
    }
    //传入state
    entry(this.state, payload)
  }
  
  dispatch(type, payload) {
    const entry = this._actions[type]
    if (!entry) {
      console.error('unkown action type');
    }
    //{commit}上下文对象就是当前实例this
    entry(this, payload)
  }
复制代码

绑定this指向

this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
复制代码

实现 getters

Vuexgetters则是借助vue的计算属性computed实现数据实时监听

this._wrappedGetters = options.getters
//外界通过$store.getters.xxx访问getters
this.getters = {};
//定义computed数据
const computed = {};
//获取当前this
const store =this;
Object.keys(this._wrappedGetters).forEach(key => {
    //this指向发生改变,用上方定义好的this
    const fn = store._wrappedGetters[key]
    //computed是无参数函数,而getters是有参数函数,所以高阶函数包一下
    computed[key] = function(){
        return fn(store.state)
    }
})
//指定getters为只读属性
Object.defineProperty(store.getters,key,{
   get: () => store._vm[key]
})

this._vm = new Vue({
  data: {
    $$state: options.state  // $$不代理,外部不能直接访问
  },
  computed
})
复制代码

vue-router原理

单页面程序中,url发生变化,不刷新显示对应视图内容

实现插件,挂载$router

// 参数1是Vue.use调用时传入的
VueRouter.install = function(_Vue) {
  Vue = _Vue;

  // 1.挂载$router属性
  //Vue.use()优先执行,此时new Vue()还没有执行,所以要延迟执行$router挂载
  // 全局混入目的:延迟下面逻辑到router创建完毕并且附加到选项上时才执行
  Vue.mixin({
    //new Vue()第一个执行的钩子函数  
    beforeCreate() {
      // 因为使用混入,钩子在每个组件创建实例时都会调用
      // 根实例才有该选项
      if (this.$options.router) {
        //挂载vue原型方便其他组件调用$router  
        Vue.prototype.$router = this.$options.router;
      }
    },
  });

复制代码

url 变化页面不刷新

hash

#/user/1 hash模式会携带’#‘,不会刷新浏览器,
通过监听hashchange,监听url变化

this.current = window.localtion.hash.slice(1) || "/";
// 监听hash变化
window.addEventListener("hashchange", () => {
  console.log(this.current);
  this.current = window.location.hash.slice(1);
});
复制代码

history

/user/1 history正常的url形式
通过监听popstate,监听url变化

数据响应式,url变化内容重新渲染

Vue.util.defineReactive定义响应式数据

 //定义响应式matched数组,存放当前路由匹配的嵌套组件
 Vue.util.defineReactive(this,'matched',[]);
 //遍历路由表routes
 this.match();
 
 match(routes){
      //路由表
      let routes = routes || this.$options.routes;
      //递归遍历路由表
      for(route of routes){
          if(route.path === '/' && this.current === '/'){
              //添加路由信息
              this.matched.push(route);
              return;
          }
          //  /about/info
          if(route.path !== '/' && this.current.indexOf(route.path) != -1){
              //包含/about
              this.matched.push(route);
              //还有嵌套子路由,递归调用match
              if(route.children.length > 0){
                  this.match(route.children)
              }
              return;
          }
      }
  }
}
复制代码

实现两个全局组件 router-link 和 router-view

实现router-link

实现一个a标签 <a href="https://juejin.cn/post/xxx">user</a>

Vue.component("router-link", {
    props: {
      to: {
        type: String,
        required: true,
      },
    },
    render(h) {
      // <a href="https://juejin.cn/post/to">xxx</a>
      // return <a href={'#'+this.to}>{this.$slots.default}</a>
      return h(
        "a",
        {
          attrs: {
            href: "#" + this.to,
          },
        },
        this.$slots.default
      );
    },
  });
复制代码

实现router-view

判断router-view深度,实现路由嵌套;通过matched数组渲染嵌套组件

Vue.component("router-view", {
    render(h) {
      //标记当前router-view深度
      this.$vnode.data.routerView = true;  
      //深度标识 
      let depth = 0;
      //向上查找routerView
      let parent = this.$parent;
      while(parent){
          const vnodeData = parent.$vnode && parent.$vnode.data;
          if(vnodeData.routerView){
              //说明parent是个router-view
              depth++;
          }
          parent = parent.$parent;
      }
      // 获取当前路由对应的组件
      let component = null;
      //matched是响应式的所以动态渲染路由
      const route = this.$router.matched[depth];
      if (route) {
        component = route.component
      }
      console.log(this.$router.matched, component);
     
      return h(component);
    },
  });
};
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享