Vuex
使用例子
import Vue from 'vue'
import Vuex from 'vuex'
import App from './app.js'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
const root = document.createElement('div')
root.id = 'app'
document.body.appendChild(root)
new Vue({
store,
render:(h)=> h(App)
}).$mount(root)
Vue.use(vuex)
Vue.use(vuex) 会执行 vuex 中的 install 方法,它的定义如下:
function install (_Vue) {
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 = _Vue;
applyMixin(Vue);
}
// applyMixin
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
}
}
// vuexInit
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;
}
}
Vux 的 install 方法执行的就是 applyMixin 方法, applyMixin 方法的主要功能将初始化 Vue 实例时传入的 store 设置到 this.$store 属性上, 子组件则从其父组件引用 $store 属性, 层层嵌套进行设置. 这样,任何一个组件都能通过 this.$store 的方式访问 store 对象了
单从这里会有疑问就是只在 this 上添加 $store 属性是不会有响应功能的,但是实现使用时修改 state 是可以触发视图更新,在哪做的了?
Vuex的响应机制
在 vuex.Store 的构造方法中有以下代码:
// 初始化 store 的响应机制(包括将 _wrappedGetters 初始为 compute)
resetStoreVM(this, state);
// resetStoreVM(this, state);
function resetStoreVM (store, state, hot) {
var oldVm = store._vm;
// bind store public getters
store.getters = {};
// reset local getters cache
store._makeLocalGettersCache = Object.create(null);
var wrappedGetters = store._wrappedGetters;
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
var silent = Vue.config.silent;
Vue.config.silent = true;
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
Vue.config.silent = silent;
}
上面代码通过实例化一个新 Vue 实例,给 $$state 和 computed 添加响应机制,看到这里还是不明白这里 new Vue 添加的响应跟启动项目的 new Vue 有啥关系?
下面通过一个例子理解一下:
<div id="app">
<h1>{{arr}}</h1>
<h1>{{$$obj}}</h1>
</div>
const obj = {a: 'l'}
new Vue({
data: {
$obj: obj
}
})
var app = new Vue({
el: '#app',
data: {
arr: [0]
},
obj,
beforeCreate(){
this.$$obj = this.$options.obj
},
mounted(){
console.log(this)
},
})
上面例子中启动项目的 Vue 没有直接在 data 中定义 $$obj 属性,$$obj 属性是在 beforeCreate 钩子函数赋值的,取至 $options 上属性 obj,$options.obj 是在 new Vue 中传入的,且这个 obj 属性也在另一个 new Vue 中使用且是使用在 data.$obj 属性上,也在就说这个 $obj 属性在这个 Vue 中是响应式的。也因此在应用中访问$$obj 时,全局的 Watcher 将会被另一个 Vue 中的 $obj 属性收集
不得不说这波操作有点骚~
getter实现
getters 则是借助 Vue 的计算属性 computed 来实现的
先回顾一下 getters 的用法: { key: fn }
然后先看这段代码逻辑:
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
上面的作用就是遍历 wrappedGetters 生成一个 compunted 的格式,先看下 wrappedGetters 怎么来的
下面是跟 wrappedGetters 有关系的代码:
function registerGetter (store, type, rawGetter, local) {
if (store._wrappedGetters[type]) {
{
console.error(("[vuex] duplicate getter key: " + type));
}
return
}
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
};
}
module.forEachGetter(function (getter, key) {
var namespacedType = namespace + key;
registerGetter(store, namespacedType, getter, local);
});
Module.prototype.forEachGetter = function forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn);
}
};
function forEachValue (obj, fn) {
Object.keys(obj).forEach(function (key) { return fn(obj[key], key); });
}
代码分析:
首先
Module原型上定义了一个forEachGetter方法,它接受一个参数fn,函数主体中的this._rawModule.getters就是我们在 Vuex 定义的getters然后看
module.forEachGetter的调用,此时传入了一个函数fn,forEachGetter内部执行forEachValue遍历我们的this._rawModule.getters(getters),对于每一个getters项都会执行传入的fn函数,执行fn时传入两个参数:第一个是getters对应的计算函数;第二参数对应getters的key回到这个
fn方法本身,重点就是执行registerGetter(store, namespacedType, getter, local);,它的功能就是重新打包了getters,仍是已{key: fn}的格式将当前getters方法保存到store._wrappedGetters对象中。只是这时的getters-key加了命名空间,getters-计算方法重新打包后注入了local.state、local.getters、store.state、store.getters四个参数,我们在使用getters可以访问到的四个参数就是从这里来的
创建 computed
上面明白了 wrappedGetters 的由来,回到创建 computed
var computed = {};
forEachValue(wrappedGetters, function (fn, key) {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
computed[key] = partial(fn, store);
Object.defineProperty(store.getters, key, {
get: function () { return store._vm[key]; },
enumerable: true // for local getters
});
});
/**
* forEach for object
*/
function forEachValue (obj, fn) {
Object.keys(obj).forEach(function (key) { return fn(obj[key], key); });
}
// partial
function partial (fn, arg) {
return function () {
return fn(arg)
}
}
根据代码可以看到也是使用 forEachValue 遍历 wrappedGetters 给 computed 赋值的,forEachValue 的作用就是遍历 wrappedGetters,然后执行传入的函数参数
执行参数函数时第一个参数 fn 就是每个 getter 的计算函数,第二个参数就是 getter 的 key,内部就是通过 computed[key] = partial(fn, store) 给 computed 添加属性,通过 partial(fn, store) 再次打包 getter 的计算函数,并把 store 参数传入,也就是对应上文 wrappedGetter 中的 store 参数
然后使用 Object.defineProperty 将 store.getters 属性的访问代理到 store._vm 中
然后在实例 Vue 时,将上面的 computed 传入使用
store._vm = new Vue({
data: {
$$state: state
},
computed: computed
});
总的来说 Vue 中的 computed 包含了我们的 getters
当我们直接使用 store._vm[nameapce+key] 访问 getters 时,会从store._vm[nameapce+key] 找,store._vm 就是 Vue 实例,也是会从 computed[nameapce+key] 中去找
所以当我们通过 store.getters[nameapce+key] 去找 getters 时,之后由上
Q&A
为什么 MUTATION 只能是同步的?
为了 devtool 快照可以更正确得记录状态的变化
异步操作是成功还是失败是不可预测的,会产生副作用