[vue2]熬夜编写为了让你们通俗易懂的去深入理解vuex并手写一个

581 阅读2分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

更多文章

[vue2]熬夜编写为了让你们通俗易懂的去深入理解nextTick原理

[vue2]熬夜编写为了让你们通俗易懂的去深入理解vue-router并手写一个

[vue2]熬夜编写为了让你们通俗易懂的去深入理解v-model原理

[vue2]熬夜编写为了让你们通俗易懂的去深入理解双向绑定以及解决监听Array数组变化问题

熬夜不易,点个赞再走吧

思路

在main.js中

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

Vue.config.productionTip = false

new Vue({
el'#app',
router,
store,
components: { App },
template'<App/>'
})

声明了store,是为了在实例中能够访问

在/store/index中


Vue.use(Vuex);

install 注册vuex实例,让外部组件可以轻松通过this.$store获取。并且获取状态并且修改状态

因为在main.js中引用了它,所以需要导出一个Store


export default new Vuex.Store({
    state: {},
    mutations:{},
    actions:{},
    modules:{},
    getters:{}
});

state

单一状态树,保存数据,和Vue实例中data遵循相同规则

mutations

  1. mutation必须是同步的
  2. 在 mutation 中混合异步调用会导致程序很难调试
  3. 为了区分有了action的概念

actions

  1. Action 提交的是 mutation,而不是直接变更状态。
  2. Action 可以包含任意异步操作

modules

如果所有数据保存在一个对象,以后会很臃肿,所以有了模块化,模块的集合,就是modules

getters

store的计算属性

测试

现在我们给state中声明一个count,并且在mutation和action中写入能改变count的方法


state: {
  count:0
},
mutations:{
  add(state){
    state.count++
  }
},
actions:{
  asyncAdd({commit}){
    setTimeout(() => {
      commit('add')
    }, 1000);
  }
}

开始

这里测试store运行正常,接下来需要自己手写vuex这几个功能并且让它运行正常,现在新建一个vuex.js ,并自行改一下路径Vuex的导入路径

这里因为功能是在Vuex.Store中实现,包含state, getters, muations, action 等可选属性

而Vuex要用install进行注册

所以这里导出一个Store和一个install

// 实现Vuex.use(Vuex)中的Vuex插件
let Vue
class Store {
  constructor(options) {
    console.log(Vue,options)
  }
}
function install(_Vue) {
  Vue = _Vue
}
export default { Store, install }

注册store

这里跟注册vue-router一样

具体参考我上一篇文章手写vue-router

利用mixin混入在尽早的生命周期内进行注册挂载

// 注册$store
  Vue.mixin({
    beforeCreate() {
      if(this.$options.store){
        console.log(this.$options.store)
        Vue.prototype.$store = this.$options.store
      }
    },
  })

这个时候已经注册了store,但是却获取不了count

count在state里,所以我们需要一个state

state

加上$$,目的是藏起来不让Vue做代理,不让直接通过state去访问它,具体源码在vuex.js:689行

this._vm = new Vue({
  data(){
    return{
      $$state: options.state
    }
  }
})

那怎么去访问呢,提供了一个get/set的方法,具体源码在vuex.js:443-451行

get state(){
  console.log(this._vm,'this._vm')
  return this._vm._data.$$state
}
set state(v){
  console.error("不能直接覆盖state,可以使用过replaceState");
}

现在刷新一下,发现可以打印出来并且不报错了

mutation

我们看到打印的options里有这些东西

那我们为了方便,把它提出来,后面会用上

constructor(options) {
  this._mutations = options.mutations
  this._actions = options.actions
}

通过mutation的使用方法看,会通过commit进入到mutation的方法里

而mutation的方法里会接收两个参数

'add'是对应的方法名

可以接收到的是状态树state和载荷playload

// commit('add', 1)
// ----------------
// mutations:{
//    add(state, n){
//      state.count += n
//    }
//  },  

所以我们需要在里面写入commit的方法

// commit
commit(type, payload){
  console.log(type, payload,'type, payload')
  const mutation = this._mutations[type]
  if(!mutation){
    console.error("找不到对应的mutation")
    return
  }
}

然后在commit的操作里,我们最后会进入到mutation里 加入以下代码

// commit通过type找到mutations中对应的方法后,会把参数传给它
mutation(this.state, payload)

最后的commit是这样

// commit
commit(type, payload){
  console.log(type, payload,'type, payload')
  const mutation = this._mutations[type]
  if(!mutation){
    console.error("找不到对应的mutation")
    return
  }
  // commit通过type找到mutations中对应的方法后,会把参数传给它
  mutation(this.state, payload)
}

action

接下来是action,action跟mutation的区别是可跟踪和不可跟踪,异步和同步,所以有异曲同工之妙,如此如此,cv一下

// dispatch
  dispatch(type, payload){
    const action = this._actions[type]
    if(!action){
      console.error("找不到对应的action")
      return
    }
  }

这里与commit中的传参有些变化,action中接收的上下文ctx中包含commit, dispatch, state等,因此第一个参数要改为this

 mutation(this, payload)

最后是

// dispatch
dispatch(type, payload){
  const action = this._actions[type]
  if(!action){
    console.error("找不到对应的action")
    return
  }
  // 这里与commit中的传参有些变化,action中接收的上下文ctx中包含commit, dispatch, state等,因此第一个参数要改为this
  action(this, payload)
}

这里执行下会报错

[Vue warn]: Error in v-on handler: "ReferenceError: mutation is not defined"

为什么呢,因为之前store里我们写到

 actions:{
    asyncAdd({commit}){
      setTimeout(() => {
        commit('add')
      }, 1000);
    }
  },

这里面包含setTimeOut这种方法时,this容易混乱,那么需要绑定一下this,让它指向store实例

this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)

这样再次执行就成功了

getters

这里还是像保存_mutations和_actions一样

constructor(options) {
  this._getters = options.getters
}

然后它本身相当于是个计算属性,那么会想到computed

在_vm里声明一下

this._vm = new Vue({
  data() {
    return {
      $$state: options.state
    }
  },
  computed
})

保存一个getters对象,之后在组件里调用的就是这个对象

this.getters = {}
const computed = {}

getters是个对象,传入的时候在getters里是个方法, 所以需要把对象的方法,变成对象的属性返回, 并接受一个state 参数

//遍历对象的方法
Object.keys(this._getters).forEach(key=>{
})

在循环里进行以下操作

获取用户定义的getter,保存在一个变量里

 const fn = this._getters[key]

这个fn就是getter方法,遍历到computed对象里

console.log(fn)
computed[key] = () => {
  return fn(this.state)
}

响应式为getters定义只读属性

Object.defineProperty(this.getters, key, {
  get: () => {
    console.log(this._vm[key]) // count的值
    console.log(this.getters) // 这个时候对象里已经有值了
    return this._vm[key]
  }
})  

这个时候打印this.getters,对象里有了一个doubleCount

组件模板里加入一行

 <p>getters: {{$store.getters.doubleCount}}</p>

现在来看看效果