【vuex原理篇】避免只懂api调用的窘境

501 阅读2分钟

vuex原理篇,以能否造轮子衡量学习效果

源代码链接

github.com/kkxiaojun/k…

背景

使用vuex进行数据状态统一管理,已经有一段时间了。但是除了看文档粘贴、复制api之外,貌似并没有什么收获。

对于一些需要深入了解的内容:

actions怎么实现的

mutations怎么实现的

getters怎么实现的

...

于是乎,赶紧的,学呀学,输出一些手写vuex核心代码的过程。

Vuex核心api实践

归根结底,vuex,就是一个vue的插件。

参考vue的插件机制

cn.vuejs.org/v2/guide/pl…

要实现的就是Vue.use(Vuex),这是vue安装插件的机制,需要Vuex对外暴露一个install方法,会把Vue传递给install这个函数

话不多说,开始...

项目准备

用vue-cli创建一个基本的项目

demo,github传送门

github.com/kkxiaojun/k…

1. Vue插件实现

Vue插件要暴露install方法。并且在Vue.prototype上挂载$store。然后导出installStore实例即可

class Store {
  constructor() {
    this.name = 'dajun'
  }
}


function install(Vue) {
  Vue.prototype.$store = new Store()
}
export default { Store, install }

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false

console.log('store===>', store)

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

App.vue

  created() {
    console.log('this.$store', this.$store)
  },

2. store的实现

import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false

console.log('store===>', store)

new Vue({
  store,
  render: h => h(App),
}).$mount('#app')

看看main.js,store是通过new Vue传入

通过什么来让每个组件都能获取$store实例呢?

我们需要使用mixinbeforeCreate来挂载,这样才能通过this.$option获取传递进来的store

然后区分根组件和子组件

// vue插件机制
function install (vue) {
  Vue = vue
  Vue.mixin({
    beforeCreate() {
      // 根组件才有store
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 子组件
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}

这里有几个疑问点:

  1. mixin的作用是将mixin的内容混合到Vue的初始参数$options中;
  2. 为什么是beforeCreate?这部分,要了解生命周期的初始化,created时,$option已经初始化好了。

关于几个疑问点: 有群友问到一个问题,我觉得很有意思,就是说直接通过$store.state.xx = ""。可以吗?其实这样赋值也不会有问题,而且state依旧是响应式的。那么为什么用commit来多此一举呢?

  1. vuex能够记录每一次state的变化记录,保存状态快照,实现时间漫游/回滚之类的操作。

关于全局变量

  1. 没有记录,二是不容易被发现

3. state

单项数据渲染:单纯的传参

class Store {
  constructor(options = {}) {
    this.state = options.state
  }
}
import Vue from 'vue'
import Vuex from './myVuex'

Vue.use(Vuex)

const vuexObj = new Vuex.Store({
  state: {
    num: 0
  }
})

export default vuexObj

打印侃侃, 没有问题

考虑到,state应该是响应式的,接着弄...

4. 响应式

比较简单的方式,就是借助Vue的响应式, new Vue()并传入响应式data

let Vue
class Store {
  constructor(options = {}) {
    this.vm = new Vue({
      data:{
        state: options.state
      }
    })
  }
}

// vue插件机制
let install = function (vue) {
  Vue = vue
  vue.mixin({
    beforeCreate() {
      // 根组件才有store
      if (this.$options && this.$options.store) {
        vue.prototype.$store = this.$options.store
      } else {
        // 子组件
        vue.prototype.$store = this.$parent && this.$parent.$store
      }
    }
  })
}

export default {
  Store,
  install
}

5. getter

可以使用Object.defineProperty代理一个getter,获取getter的值,执行函数计算。最后挂载到$store.getters

defineGetters(options) {
  this.getters = {}
  let getters = options.getters || {}
  Object.keys(getters).forEach(key=>{
      Object.defineProperty(this.getters, key, {
          get:()=>{
            console.log('this.state', this.state)
            return getters[key](this.state)
          }
      })
  })
}
const vuexObj = new Vuex.Store({
  state: {
    num: 0
  },
  getters: {
    getNum(state) {
      return state.num
    }
  },
})

6. mutation

参考一下vuexmutaion的使用,只需要记录函数,commit的时候更新数据

class Store {
  constructor(options = {}) {
    // 增加响应式
    this.vm = new Vue({
      data:{
        state: options.state
      }
    })
    // mutations
    this.defineMutations(options)
  }
  defineMutations(options) {
    this.mutations = {}
    let mutations = options.mutations || {}
    Object.keys(mutations).forEach(mutationName=>{
        this.mutations[mutationName] = (arg) => {
          mutations[mutationName](this.state, arg)
        }
    })
  }
  commit = (method, arg) => {
    console.log(`commit:mutations:${method}===>`, method)
    this.mutations[method](arg)
  }
  // 为了能直接访问state
  get state() {
    return this.vm.state
  }
}

7. action

action的实现,基本和mutation类似

class Store {
  constructor(options = {}) {
    // 增加响应式
    this.vm = new Vue({
      data:{
        state: options.state
      }
    })
    // actions
    this.defineActions(options)
  }
  defineActions(opotions) {
    this.actions = {}
    let actions = opotions.actions
    Object.keys(actions).forEach(actionName => {
      this.actions[actionName] =(arg) => {
        // 箭头函数,不绑定this。这里this就是$store实例
        actions[actionName](this, arg)
      }
    })
  }
  dispatch(method, arg) {
    console.log(  `dispatch:actions:${method}===>`, method)
    this.actions[method](arg)
  }
  commit = (method, arg) => {
    console.log(`commit:mutations:${method}===>`, method)
    this.mutations[method](arg)
  }
  // 为了能直接访问state
  get state() {
    return this.vm.state
  }
}

注意点: {commit} 就是对thisstore实例的解构

最终效果

定眼一看,确实有点儿简陋(轮子再小,也是轮子?)

当然,还有mapState,mapMutations,modules的实现。感兴趣,大家可以自行实践一下

demo链接

最后,附上demo链接

github.com/kkxiaojun/k…