vuex集中式存储管理应用所有组件的状态,并以响应的规则保证状态以可预测的方式 发生变化。
步骤:
1.Store类,保存选项,_mutations,_actions,getters
2.响应式状态:new Vue方式设置响应式。
3.get state 和 set state commit dispatch
4.install方法 挂载store到vue的原型对象上,所有实例都可以读取到。
说到底,vuex也还是个插件,所以生成一个类,按照使用插件的形式将其export导出。
挂载到Vue实例上我们还是使用mixin的方式,原理和vue-router的自定义方式一致,这里就不多说了。
let Vue
class Store{
constructor(options){
//保存选项
// state为响应式数据
this.state = new Vue({
data:options.state
})
}
}
function install(_Vue){
Vue = _Vue
//挂载$store给外面使用
Vue.mixin({
beforeCreate() {
if(this.$options.store){
//挂载到vue原型上,每个vue实例都可以访问到
Vue.prototype.$store = this.$options.store
}
},
})
}
export default {
Store,install
}
但是在这里,我们使用new Vue的data属性来让state成为一个响应式的数据,有个弊端就是能够直接的修改data里的数据,那这样违反了vuex单向数据流的特点,那么我们要封装一下。
官网解释,有两个的$变量就不做代理,所以也就不会再_vm的根上访问到$$state这个属性,同时设置set get俩属性,get为只读,那么外面就无法修改state的值,只能通过commit或actions进行修改。_vm下的响应式对象会挂载在_data的对象上,_vm下的$data是一个原始对象,不具备响应式,所以使用_data。
如下图,$data不存在observer
class Store{
constructor(options){
//保存选项
// state为响应式数据
this._vm = new Vue({
data:{
$$state:options.state
}
})
}
//给用户暴露接口
get state(){
console.log(this._vm);
return this._vm._data.$$state
}
set state(val){
throw Error('replaceSate')
}
}
响应式的数据发生变化就会引起render函数渲染数据 ,在commit中会接收到参数,我们在构造函数中存储传入的mutations和actions,然后在commit和dispach中匹配到详情的操作,然后执行。
但是在执行过程中有坑,this的指向问题:当前的调用是在外部,所以指向的是外部的store实例,是没有commit等参数,所以没有办法调用到。需要在constructor存储上下文,并且改变this指向到当前的实例,获取到对应的上下文即可。
class Store{
constructor(options){
//保存选项
this._mutations = options.mutations||{}
this._actions = options.actions||{}
// state为响应式数据
this._vm = new Vue({
data:{
$$state:options.state
}
})
//上下文的绑定
this.commit=this.commit.bind(this)
this.dispatch=this.dispatch.bind(this)
}
//给用户暴露接口
get state(){
return this._vm._data.$$state
}
set state(val){
throw Error('replaceSate')
}
//store.commit(type,payload)
commit(type,payload){
//获取mutitions
const entry = this._mutations[type]
if(!entry){
console.error('unknown mutition type');
}
entry(this.state,payload)
}
dispatch(type,payload){
const entry = this._actions[type]
if(!entry){
console.error('unknown actions type');
}
console.log(this);
entry(this,payload)//注意这里的this指向问题,当前的调用是在外部,所以指向的是外部的store实例,是没有commit等参数,所以没有办法调用到。需要在constructor存储上下文,并且改变this指向。
}
}
getters我们可以借助computed属性,只可获取不可更改,获取到getters的key给到computed,并且在给一个函数,在其内部调用fn并且传入state,再将computed属性进行响应式处理。
this._wrappedGetters = options.getters||{}
//定义computed选项
const computed={}
this.getters={}
const store= this
Object.keys(this._wrappedGetters).forEach(key=>{
//获取用户定义的getter
const fn = store._wrappedGetters[key]
// 转换为computed可以使用的无参数形式
computed[key]=function(){
return fn(store.state)
}
//为getters定义只读属性
Object.defineProperty(store.getters,key,{
get:()=>{
return store._vm[key]
}
})
})
// state为响应式数据
this._vm = new Vue({
data:{
$$state:options.state
},
computed
})
附完整代码:
let Vue
class Store{
constructor(options){
//保存选项
this._mutations = options.mutations||{}
this._actions = options.actions||{}
this._wrappedGetters = options.getters||{}
//定义computed选项
const computed={}
this.getters={}
const store= this
Object.keys(this._wrappedGetters).forEach(key=>{
//获取用户定义的getter
const fn = store._wrappedGetters[key]
// 转换为computed可以使用的无参数形式
computed[key]=function(){
return fn(store.state)
}
//为getters定义只读属性
Object.defineProperty(store.getters,key,{
get:()=>{
return store._vm[key]
}
})
})
// state为响应式数据
this._vm = new Vue({
data:{
$$state:options.state
},
computed
})
//上下文的绑定
this.commit=this.commit.bind(this)
this.dispatch=this.dispatch.bind(this)
}
//给用户暴露接口
get state(){
return this._vm._data.$$state
}
set state(val){
throw Error('replaceSate')
}
//store.commit(type,payload)
commit(type,payload){
//获取mutitions
const entry = this._mutations[type]
if(!entry){
console.error('unknown mutition type');
}
entry(this.state,payload)
}
dispatch(type,payload){
const entry = this._actions[type]
if(!entry){
console.error('unknown actions type');
}
entry(this,payload)//注意这里的this指向问题,当前的调用是在外部,所以指向的是外部的store实例,是没有commit等参数,所以没有办法调用到。需要在constructor存储上下文,并且改变this指向。
}
}
function install(_Vue){
Vue = _Vue
//挂载$store给外面使用
Vue.mixin({
beforeCreate() {
if(this.$options.store){
//挂载到vue原型上,每个vue实例都可以访问到
Vue.prototype.$store = this.$options.store
}
},
})
}
export default {
Store,install
}