Vuex3.x 源码学习

Vuex 是专为 Vue.js 应用程序开发的状态管理模式,它可以实现我们 Vue 中跨级组件的通信,也可以存储全局共享的状态;它主要通过 state 来定义状态,通过 commitdispatch 来同步或异步修改状态;

图片

一、基础用法
  • 安装依赖:npm install vuex
  • 编写 store 实例并导出;
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
    
    
    // 全局状态
    state: {
    
    
        count: 1
    },
    // 存储同步修改 state 中变量的方法
    mutations: {
    
    },
    // 存储异步操作的方法
    actions: {
    
    },
    // 相当于 computed 的作用
    getters: {
    
    },
    // 子模块
    modules: {
    
    }
})
  • 在 main.js 中进行注册;
// main.js
import Vue from 'vue'
import store from './store/index.js'
import App from './App.vue'
new Vue({
    
    
    store,
    render: h => h(App)
}).$mount('#app')
  • 在页面中使用:

(1)在 computed 属性中进行定义

<script>
export default {
   computed: {
       count() {
           return this.$store.state.count
       }
   } 
}
</script>

(2)通过 mapState 获取

<script>
import { ...mapState } from 'vuex'
export default {
   computed: {
       // 方式一
       ...mapState([
           'count'
       ])
       // 方式二
       //...mapState({
       //    count: state => state.count
       //})
       // 方式三: 命名空间
       //...mapState('xxx', [
       //	   'count'
       //])
   }
}
</script>
二、基本原理

了解完基础用法之后,接下来通过源码来理解其内部实现;

  • 首先是通过 Vuex.use(Vuex) 来进行插件安装,那我们知道 Vue.use 是通过调用插件的 install 方法来安装,那么 Vuex 的实现需要有一个 install 方法;
  • 实例化 Vuex 是 new Vuex.Store({}),则需要有一个 Store类或方法;
  • mapStatemapMutationsmapActionsmapGetters 实现;
1、install 方法
// src/store.js
export function install(_Vue) {
    
    
    if (Vue && _Vue === Vue) {
    
    
        if (__DEV__) {
    
    
            console.error(
                '[vuex] already installed. Vue.use(Vuex) should be called only once.'
            )
        }
        return
    }
    Vue = _Vue
    // 注入的核心
    applyMixin(Vue)
}
// src/mixin.js
export default function (Vue) {
    
    
    const version = Number(Vue.version.split('.')[0])
    // 版本判断,如果是 2.x,则在全局 beforeCreate 生命周期中插入
    if (version >= 2) {
    
    
        Vue.mixin({
    
     beforeCreate: vuexInit })
    } else {
    
    
        const _init = Vue.prototype._init
        Vue.prototype._init = function (options = {
     
     }) {
    
    
            options.init = options.init
                ? [vuexInit].concat(options.init)
            : vuexInit
            _init.call(this, options)
        }
    }
    function vuexInit () {
    
    
        const options = this.$options
        // 根实例注入 $store
        if (options.store) {
    
    
            this.$store = typeof options.store === 'function'
               	? options.store()
            	: options.store
        } else if (options.parent && options.parent.$store) {
    
    
            // 从父节点获取 store 实例
            this.$store = options.parent.$store
        }
    }
}

可以看到 install 做的事情比较简单,install 方法主要将 $store 注入到组件或页面中,2.x 版本及以上是通过 beforeCreate 注入,其他的则通过在 init 方法中注入,类似于 $mount 方法;

2、Store 类

Store 类主要是对传入的 options 进行处理,包括模块收集、安装模块、处理父子模块之间的关系等,可以参考简略版vuex 的代码,需要注意的是命名空间的处理,调用孙子模块需要确保父模块的路径名称正确

3、mapState、mapMutations、mapActions、mapGetters 的实现

mapState、mapMutations、mapActions、mapGetters 工具方法可以帮助我们一次性获取多个 store 中的变量和方法,内部实现也比较简单,主要分几个步骤:

  • 对用户传入的参数进行处理 normalizeNamespace
  • 对用户传入的参数进行格式化 normalizeMap,然后循环遍历取值赋值给返回结果;
  • 如果有命名空间,则根据命名空间获取相应的模块 getModuleByNamespace,返回该模块的数据;
  • 不同类型的实现可以参考本文第二点代码仓库的代码,位置在 helper.js 文件;
// 将数据转化为 map, 格式为 { key, val }
// (1) 参数为数组: mapState(['xxx'])
// (2) 参数为对象: mapState({ xxx: store => store.state.xxx })
function normalizeMap(map) {
    
    
    return Array.isArray(map)
        ? map.map(key => ({
    
     key, val: key }))
        : Object.keys(map).map(key => ({
    
     key, val: map[key] }))
}

// 对参数和 namespace 进行处理
// (1) 不带命名空间: mapState([])
// (2) 带命名空间: mapState('namespace', []), 对命名空间进行处理,保持 namespace/ 的格式
function normalizeNamespace(fn) {
    
    
    return (namespace, map) => {
    
    
        if (typeof namespace !== 'string') {
    
    
            map = namespace
            namespace = ''
        } else if (namespace.charAt(namespace.length - 1) !== '/') {
    
    
            namespace += '/'
        }
        return fn(namespace, map)
    }
}

// 根据命名空间获取相应的模块
function getModuleByNamespace(store, helper, namespace) {
    
    
    const module = store._modulesNameSpaceMap[namespace]

    if (!module) {
    
    
        console.error(`[vuex] module namespace not found in ${
      
      helper}(): ${
      
      namespace}`)
    }

    return module
}
三、总结

Vuex 是专门为 Vue 提供状态管理的插件,注册之后可以在全局访问到 $store 实例,同时也提供一些 api 来辅助我们获取实例中的状态和状态变更的方法,其内部实现主要是根据传入的 options 来进行模块间关系的处理,也可以通过命名空间来快速访问状态,方便我们在日常开发使用;

猜你喜欢

转载自blog.csdn.net/Ljwen_/article/details/125501803