vue学习之vuex基础详解及源码解读(一)
概念
Vuex是一个专为Vue.js应用程序开发的状态管理模式。当项目比较庞大的时候,每个组件的状态很多,为了方便管理,需要把组件中的状态抽取出来,放入Vuex中进行统一管理。
每一个 Vuex 应用的核心就是store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:
Vuex的状态存储是响应式的。当 Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
你不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
简单应用
构建vue工程vue init webpack vuexStudy
构建后目录结构
其中:
index.js
import Vue from 'vue' import Vuex from 'vuex' //如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex) Vue.use(Vuex); export default new Vuex.Store({ state: { //存放组件之间共享的数据 //在组件通过this.$store.state.count获取 count: 0 }, mutations: { //显式的更改state里的数据,不能用于处理异步事件 //组件中通过this.$store.commit('incrementByStep');调用 incrementByStep(state) { state.count++; } }, getters:{ //如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算 }, actions:{ //类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作 } });
new Vuex.Store({}) 表示创建一个Vuex实例,通常情况下,他需要注入到Vue实例里。 Store是Vuex的一个核心方法,字面上理解为“仓库”的意思。Vuex Store是响应式的,当Vue组件从store中读取状态(state选项)时,若store中的状态发生更新时,他会及时的响应给其他的组件而且不能直接改变store的状态,改变状态的唯一方法是显式地提交更改。
main.js引入vuex
import Vue from 'vue' import App from './App' //vuex文件 import store from './store' Vue.config.productionTip = false; /* eslint-disable no-new */ new Vue({ el: '#app', //引入 store, components: { App }, template: '<App/>' })
APP.vue引用了counter这个组件
<div id="app"> <!--<img src="./assets/logo.png">--> <!--<HelloWorld/>--> <counter/> </div> </template> <script> import HelloWorld from './components/HelloWorld' import counter from './components/counter' export default { name: 'App', components: { //HelloWorld counter } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
counter.vue定义counter组件
<template> <div id="counterContent"> <div>{{ count }}</div> <button v-on:click="addCount">请点击</button> </div> </template> <script> export default { name: "counter", computed: { count () { return this.$store.state.count } }, methods:{ addCount(){ debugger; this.$store.commit('incrementByStep'); } } } </script> <style scoped> #counterContent{ background-color: blue; width:200px; height:50px; } </style>
通过npm run dev
启动项目,最终的结果如图:
源码解读
node添加Vuex依赖下载的vuex文件(node_modules目录下)如下:
其中vuex.common.js在预编译调试时,CommonJS规范的格式,可以使用require("")引用的NODEJS格式。
vuex.esm.js在预编译调试时, EcmaScript Module(ES MODULE),支持import from 最新标准的。
vuex.js直接用在<script>标签中的,完整版本,直接就可以通过script引用。
而vuex的源码托管在https://github.com/vuejs/vuex,这里研究git上的源码。
入口
Vuex 源码的入口是 src/index.js。
import { Store, install } from './store' import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers' export default { Store, install, version: '__VERSION__', mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }
这是Vuex对外暴露的API。其中, Store是Vuex提供的状态存储类,通常我们使用Vuex就是通过创建 Store的实例。接着是install方法,这个方法通常是我们编写第三方Vue插件的时候使用。
install
install是在store.js内暴露的方法
当Vue通过npm安装到项目中的时候,我们在代码中引入第三方Vue插件需要添加下列代码
import Vue from 'vue' import Vuex from 'vuex' //如果在模块化构建系统中,请确保在开头调用了 Vue.use(Vuex) Vue.use(Vuex);
执行Vue.use(Vuex)的时候,其实就是调用了install的方法并传入Vue的引用。
Vue.use
Vue.use = function (plugin) { var installedPlugins = (this._installedPlugins || (this._installedPlugins = [])); if (installedPlugins.indexOf(plugin) > -1) { return this } // additional parameters var args = toArray(arguments, 1); args.unshift(this); if (typeof plugin.install === 'function') { //调用vuex的install plugin.install.apply(plugin, args); } else if (typeof plugin === 'function') { plugin.apply(null, args); } installedPlugins.push(plugin); return this }; }
install
//判断Vue是否已存在,保证install方法只执行一次 if (Vue && _Vue === Vue) { if (process.env.NODE_ENV !== 'production') { console.error( '[vuex] already installed. Vue.use(Vuex) should be called only once.' ) } return } //赋值给Vue变量,index.js文件的其它地方使用Vue这个变量 Vue = _Vue //调用了 applyMixin 方法,给 Vue 的实例注入一个 $store 的属性 applyMixin(Vue) }
plugin参数:
args参数:
var applyMixin = function (Vue) { //获取版本信息,这里是2 var version = Number(Vue.version.split('.')[0]); if (version >= 2) { //调用vuexInit Vue.mixin({ beforeCreate: vuexInit }); } else { var _init = Vue.prototype._init; Vue.prototype._init = function (options) { if ( options === void 0 ) options = {}; options.init = options.init ? [vuexInit].concat(options.init) : vuexInit; _init.call(this, options); }; } //给Vue实例注入$store 的属性,可以通过this.$store.xxx访问 function vuexInit () { var options = this.$options; // store injection if (options.store) { this.$store = typeof options.store === 'function' ? options.store() : options.store; } else if (options.parent && options.parent.$store) { this.$store = options.parent.$store; } } };