vuex源码解析+手写

148 阅读1分钟

vuex的数据定义(store.js)

    import Vue from "vue";
    import Vuex from "./vuex.js";

    Vue.use(Vuex); // 初始化

    export default new Vuex.Store({

      state: { // 响应式数据
        counter: 0
      },

      mutations: { // 修改状态
        add(state, payload) {
          console.log('add payload', payload);
          state.counter += payload;
        }
      },

      actions: { // 操作,业务逻辑
        add({ commit }) {
          setTimeout(() => {
            commit("add", 2)
          }, 1000);
        }
      },

      getters: { // 计算属性
        doubleCounter(state) {
          return state.counter * 2;
        } 
      }

    });

源码实现

  1. 实现install方法用于vue插件加载
    let Vue; // 保存全局vue
    
    const install = function(_Vue) {
      Vue = _Vue;
      Vue.mixin({
        beforeCreate() {
          if (this.$options.store) { // 判断是根组件
            Vue.prototype.$store = this.$options.store; // 绑定new vuex的实例在vue的原型上,这样在vue作用域内都可以使用。
          }
        }
      })
    }
  1. 实现Store类进行数据劫持监听和事件操作
    class Store {
      constructor(options) {

        const { mutations, actions, getters, state } = options;
        this._mutations = mutations;
        this._actions = actions;
        this._getters = getters;

        const computed = this.setGetters(); // 设置getters的数据响应

        // 绑定作用域,防止用户使用时作用域丢失。
        this.commit = this.commit.bind(this);
        this.dispatch = this.dispatch.bind(this);

        this._vm = new Vue({ // 使用vue本身的数据响应式监听来进行vuex的数据监听。
          data: {
            $$state: state // 使用双$$符号前缀的属性,vue内部不会将该属性代理到_vm实例上面,实现私有化数据,不加该符号使用this.vm.state可以访问值
          },
          computed
        })
      }

      get state() {
        return this._vm.$data.$$state; // 做一层数据转接
      }

      set state(v) { // 不允许通过直接访问state设置为新的值或重置值
        console.error('please use replace state to reset state')
      }

      commit(type, payload) {
        const entry = this._mutations[type]; // 获取对应的函数
        if (entry) entry(this.state, payload);
      }

      dispatch(type, payload) {
        const entry = this._actions[type]; // 获取对应的函数
        if (entry) entry(this, payload); // 这里使用dispath的时候 传递的是当前上下文对象
      }

      setGetters() {
        this.getters = {}; // 暴露给用户使用的getters,用户可以使用$store.getters进行获取
        const computed = {};
        const store  = this; // 保存上下文,用于获取vuex实例。

        Object.keys(this._getters).forEach(key => {

          const fn = store._getters[key]; // 获取用户定义的getter函数
          computed[key] = function() { // 因为vue里面的计算属性是没有参数的,所以这里做一层高阶函数封装转接
            return fn(store.state);
          }

          // 设置给用户暴露的getters只读
          Object.defineProperty(store.getters, key, {
            get: () => store._vm[key] // 因为vue会把数据代理,所以可以直接使用_vm[key]取值。
          })

        })
        return computed;
      }
    }
  1. 导出当前实例
    export default {
      Store,
      install
    }

在页面中使用vuex

  1. 导入vuex实例
  2. 加载实例到vue
  3. 在vue组件中进行使用
    import store from './store.js'
    new Vue({
      store,
      render: h => h(App)
    }).$mount('#app')

vue组件中使用

    <template>
      <div class="home">
        <h1>This is an home page {{ $store.state.counter }}</h1>
        <h1>vuex getters{{ $store.getters.doubleCounter }}</h1>
        <button @click="$store.commit('add', 1)">add counter num</button>
        <button @click="$store.dispatch('add')">dispatch add counter num </button>
      </div>
    </template>

    <script>
    export default {
      name: "home",
      components: {
      },
      created() {
        // console.log("created home", this.$store)
        setTimeout(() => {
          // this.$store.state = { counter: 2 }
        },2000)
      }
    }
    </script>

结语

  1. 以上代码实现了vuex的核心概念,帮我们了解到了为什么vuex的数据是响应式的
  2. 让我们深入的理解了vuex的api及其功能点
  3. 如还想对vuex进行更深层次的研究请前往官方 vuex github 源码进行查看