深入浅出VueX

64 阅读3分钟

关于VueX

VueX是适用于在Vue项目开发时使用的状态管理工具。试想一下,如果在一个项目开发中频繁的使用组件传参的方式来同步data中的值,一旦项目变得很庞大,管理和维护这些值将是相当棘手的工作。为此,Vue为这些被多个组件频繁使用的值提供了一个统一管理的工具——VueX。在具有VueX的Vue项目中,我们只需要把这些值定义在VueX中,即可在整个Vue项目的组件中使用。

VueX中的核心内容

在VueX对象中,其实不止有state,还有用来操作state中数据的方法集,以及当我们需要对state中的数据需要加工的方法集等等成员。

成员列表:

  1. state 存放状态;
  2. mutations:操作state数据的方法;
  3. getters 加工state成员给外界;
  4. actions 异步操作state数据;
  5. modules 模块化状态管理。

具体怎样使用,大家可以直接查看VueX官方文档,这里不再赘述。下面用源代码的方式实现一个简易版的VueX。

源代码的方式实现简易版的VueX

这里只是VueX的简单实现,更加全面的请看下一遍文章《深入解析VueX 源码》

首先实现一个VueX的install方法,Vue.use 方法默认会调用插件的install方法,此方法中的参数就是Vue的构造函数。

Vue.use = function (plugin) {
    plugin.install(this);
}

install

install方法的源代码:

let Vue;
export const install = (_Vue) => {
  // 插件的安装 Vue.use(Vuex)
  // _Vue 是Vue的构造函数
  Vue = _Vue;
  // 需要将根组件中注入的store 分派给每一个组件 (子组件) Vue.mixin
  Vue.mixin({
    // 内部会把生命周期函数 拍平成一个数组
    beforeCreate() {
      // 给所有的组件增加$store 属性 指向我们创建的store实例
      const options = this.$options; // 获取用户所有的选项
      if (options.store) {
        // 根实例挂载store
        this.$store = options.store;
      } else if (options.parent && options.parent.$store) {
        // 儿子或者孙子依次递归从父辈获得store
        this.$store = options.parent.$store;
      }
    },
  });
};

Store构造函数

Store构造函数在实例化后,用于将VueX中的state,getters方法挂载到实例上,并且通过commit和dispatch分别调用mutations、actions对象中的函数。

Store构造函数的源代码:

// 1.功能是遍历对象
export const forEachValue = (obj, callback) => {
  Object.keys(obj).forEach((key) => callback(obj[key], key));
};

export class Store {
  // 容器的初始化
  constructor(options) {
    // options 就是你new Vuex.Store({state,mutation,actions})
    const state = options.state; // 数据变化要更新视图 (vue的核心逻辑依赖收集)

    // 响应式的数据 new Vue({data})
    // 1.添加状态逻辑  数据在哪使用 就会收集对应的依赖
    const computed = {};
    // 2.处理getters属性 具有缓存的 computed 带有缓存 (多次取值是如果值不变是不会重新取值)
    this.getters = {};
    forEachValue(options.getters, (fn, key) => {
      computed[key] = () => {
        // 将用户的getters 定义在实例上, 计算属性是如何实现环翠
        return fn(this.state);
      };

      Object.defineProperty(this.getters, key, {
        // 当我取值时 执行计算属性的逻辑
        get: () => this._vm[key],
      });
    });

    // 3.计算属性的实现
    this._vm = new Vue({
      data: {
        // 属性如果是通过$开头的 默认不会将这个属性挂载到vm上
        $$state: state, // 会将$$state 对应的对象 都通过defineProperty来进行属性劫持
      },
      computed: computed,
    });

    // 4.实现mutations
    this.mutations = {};
    forEachValue(options.mutations, (fn, key) => {
      // this.mutations = {myAge:(payload)=>用户定义的逻辑(state,payload)}
      this.mutations[key] = (payload) => fn(this.state, payload);
    });

    // 5.实现actions
     this.actions = {};
    forEachValue(options.actions, (fn, key) => {
      this.actions[key] = (payload) => fn(this, payload);
    });
    
    // 在严格模式下  actions 和 mutations是有区别
    this.commit = (type, payload) => {
      //保证当前this 当前store实例
      // 调用commit其实就是去找 刚才绑定的好的mutation
      this.mutations[type](payload);
    };

    this.dispatch = (type, payload) => {
      this.actions[type](payload);
    };
  }

  get state() {
    // 属性访问器   new Store().state  Object.defineProperty({get()})
    return this._vm._data.$$state;
  }
}

在vuex目录下的index.js将Store, install暴露出去:

import { Store, install } from './store'; //

export default {
    Store,
    install
}

使用

初始化store下index.js中的内容:

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

Vue.use(Vuex);

let store = new Vuex.Store({
  state: { count: localStorage.getItem("qqq") / 1 || 100 },
  getters: {
    myAge(state) {
      return state.count + 20;
    },
  },

  mutations: {
    syncChange(state, payload) {
      state.count += payload;
      localStorage.setItem("qqq", state.count);
    },
  },

  actions: {
    changeCountFn(store, payload) {
      setTimeout(() => {
        store.commit("syncChange", payload);
      }, 500);
    },
  },
}  

将store挂载到当前项目的Vue实例当中去

打开main.js

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,  //store:store 和router一样,将我们创建的Vuex实例挂载到这个vue实例中
  render: h => h(App)
})

在组件中使用Vuex

<template>
  <div>
    <h2>当前数字是{{$store.state.count}}</h2>
    <button @click="add">+1</button>
    <button @click="minus">-1</button>
  </div>
</template>

<script>
export default {
  name: "bro2",
  methods: {
    add() {
      this.$store.dispatch("changeCountFn", 1);
    },
    minus() {
      this.$store.commit("syncChange", -1);
    },
  },
  mounted() {
    console.log(this.$store);
  },
};
</script>

结果:

实现了一个简易版的VueX,但是还有很多功能没有实现呀,比如模块化、命名空间、持久化等等。接下来新开一遍博客,详细实现VueX源码。