vuex源码

vuex工作流程

  1. 每个组件都共享Store中的数据, 以及每个组件都可以通过 $store.state 或者 getters 拿到传入的数据,

  2. 通过事件 或者 回调函数 触发 muation,进行同步更新数据, 从而触发视图更新

  3. 通过 提交 action 进行异步操作数据,

vuex实例

main.js

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


Vue.config.productionTip = false

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

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

// 使用 了 Vue.use() 方法,官方文档是这样解释的, 可以看到使用 use() 方法,必须提供一个 install 方法
// 该方法 有两个 参数, 一个 是 Vue 的构造器, 另一个是可选项
Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        num:0,
        name:'我是测试数据',
        mill: "我是getters用的"
    },
    getters:{
        getMill(state) {
            return state.mill
        }
    },
    mutations: {
      incr(state, payload){
        state.num += payload
      },
      desr(state, payload){
          state.num -= payload
      }
    },
    actions: {
      asyncIncr({commit}, payload){
        setTimeout( () => {
          commit('incr', payload)
        }, 0)
      }
    },
    modules: {
    }
})

App.vue

<template>
  <div id="app">
    <h2>vuex</h2>
    <p>{{$store.state.name}}</p>
    <p>{{$store.getters.getMill}}</p>
    {{$store.state.num}}
    <button @click="addNum">点击加1</button>
    <button @click="AnsyncIncr">异步加一</button>
  </div>
</template>


<script>
    export default {
        mounted() {
            // setInterval(() => {
            //     this.$store.state.num += 1
            // }, 1000)
        },
        methods:{
            addNum(){
                this.$store.commit('incr',1)
            },
            AnsyncIncr(){
                this.$store.dispatch('asyncIncr', 2)
            }
        }
    }
</script>

<style lang="less">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;

  a {
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

分析

  • 首先,组件都有一个$store属性,通过new Vuex.store({}) 方法传入的这个对象的数据
    vuex源码_第1张图片
    上面代码使用了Vue.use方法,根据官方解释,使用use方法,必须提供一个install方法,该方法 有两个 参数, 一个 是 Vue 的构造器, 另一个是可选项

vuex源码_第2张图片
vuex源码_第3张图片

实现vuex

  • 在store目录下,新建myStore.js,在index.js文件中引入该文件
    vuex源码_第4张图片
  • 使用Vue.use必须提供一个install方法,第一个参数是Vue构造器,可以拿到vue一切属性与方法
const install = _Vue => {
  console.log("install")
}
 
export default {
  install
}
  • 在index.js文件中使用了new Vuex.Store()方法 所以还需要提供一个 Store的 class
let Vue
class Store {}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
    // 全局注册一个混入,影响注册之后所有创建的每个 Vue 实例
  Vue.mixin({
    beforeCreate() {
      console.log(1)
    }
  })
}
 
export default {
  install,
  Store
}

结果如下:
vuex源码_第5张图片

由于数据是共享的,每个组件都需要拿到store数据,在使用时通过this. s t o r e 拿 到 , 也 就 是 每 个 组 件 都 需 要 有 store拿到,也就是每个组件都需要有 storestore属性,可以通过$options拿到所有的属性,结果如下:

let Vue
class Store {}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      console.log(this.$options)
    }
  })
}
 
export default {
  install,
  Store
}

vuex源码_第6张图片

  • 完善install方法
let Vue
class Store {}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      //判断 根 实例 有木有传入store 数据源,
      //如果传入了, 就把它放到实例的 $store上
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 2. 子组件去取父级组件的$store属性
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
 
export default {
  install,
  Store
}
  • 通过new Vuex.Store() 方法,该方法传入一个对象,包含state, getters, muations, action 等可选属性
    vuex源码_第7张图片
    Store类必须接受一个参数,代码如下:
let Vue
class Store {
  constructor(options = {}) {
    console.log(options)
  }
}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      //判断 根 实例 有木有传入store 数据源,
      //如果传入了, 就把它放到实例的 $store上
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 2. 子组件去取父级组件的$store属性
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
 
export default {
  install,
  Store
}

结果如下:
vuex源码_第8张图片

  • 完成state源码
class Store {
  constructor(options = {}) {
    this.state = options.state
  }
}

App.vue

<template>
  <div id="app">
    <h2>vuex</h2>
    <p>{{$store.state.name}}</p>
  </div>
</template>
 
<script>
export default {}
</script>

vuex源码_第9张图片

  • 手写getters
    原生实例:
import Vue from "vue"
import Vuex from "vuex"
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  state: {
    name: "我是原生的state数据",
    mill: "我是getters用的"
  },
  getters: {
    getMill(state) {
      return state.mill
    }
  },
  mutations: {},
  actions: {},
  modules: {}
})

App.vue

<template>
  <div id="app">
    <h2>vuex</h2>
    <p>{{mill}}</p>
  </div>
</template>
 
<script>
export default {
  computed: {
    mill() {
      return this.$store.getters.getMill
    }
  }
}
</script>

vuex源码_第10张图片

  • 打印 this.$store.getters, 可以发现是一个对象
    vuex源码_第11张图片
  • 需要将getters的方法作为对象的属性返回,并且传入一个state参数
    vuex源码_第12张图片
  • 实现getters
class Store {
  constructor(options = {}) {
    this.state = options.state
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
  }
}

vuex源码_第13张图片
现在基本上实现了state,getters,但是会有一个问题,直接改 vuex 的数据是可以变化的,但是现在无法改变

错误演示

App.vue

<template>
  <div id="app">
    <h2>vuex</h2>
    <p>{{mill}}</p>
    <p>{{$store.state.num}}</p>
  </div>
</template>
 
<script>
export default {
  computed: {
    mill() {
      console.log(this.$store.getters)
      return this.$store.getters.getMill
    }
  },
  mounted() {
    setInterval(() => {
      this.$store.state.num += 1
    }, 1000)
  }
}
</script>

index.js

import Vue from "vue"
import Vuex from "./myStore"
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  state: {
    name: "我是原生的state数据",
    mill: "我是getters用的",
    num: 1
  },
  getters: {
    getMill(state) {
      return state.mill
    }
  },
  mutations: {},
  actions: {},
  modules: {}
})

然而使用 我们自己写的 发现确不变化, 那么需要来解决这个问题

我们知道 ,vue 中的数据只要是写在 data 中的,都是支持 响应式的, 所以 我们只有把 vuex 中的state 定义在 Vue中的 data中

改写代码:

class Store {
  constructor(options = {}) {
    this.state = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
  }
}

但如果这样写的 话, 就不能像原先那样调用了,需要多调一层 state才能拿到数据, (使用类的属性访问器setter, getter)解决这个问题,参考 es6文档
vuex源码_第14张图片
改写代码如下:

class Store {
  constructor(options = {}) {
    this.myState = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
  }
  get state() {
    return this.myState.state
  }
}

到这里,已经基本实现了, 数据响应式变化, state, getters 了

整体代码

let Vue
class Store {
  constructor(options = {}) {
    // 核心代码: 保证数据都是响应式的
    this.myState = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
  }
  get state() {
    return this.myState.state
  }
}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      //判断 根 实例 有木有传入store 数据源,
      //如果传入了, 就把它放到实例的 $store上
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 2. 子组件去取父级组件的$store属性
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
 
export default {
  install,
  Store
}
  • 实现commit 方法 , 进行修改数据数据,先看原生的
    vuex源码_第15张图片
    $store 下 有个 commit 方法, commit() 方法 提供两个参数, 一个 muation里定义的函数名 , 一个是传入的后续参数
  let mutations = {} // 定义一个对象收集所有传入的 mutations 
 
  Object.keys(options.mutations).forEach(key => {
     mutations[key] = () => {
     
    }
  })
  • commit方法实现
//  提供commit 方法
this.commit = (key, payload) => {
    mutations[key](payload)
}
  • 所以上边 mutations 的订阅 ,它收集的是所有方法, 需要遍历所有的key , 所以取出所有的key 进行 一次执行
 //  定义 muations
 let mutations = {}
 Object.keys(options.mutations).forEach(key => {
   mutations[key] = payload => {
     options.mutations[key](this.state, payload)
   }
 })
  • 整理代码如下:
let Vue
class Store {
  constructor(options = {}) {
    // 核心代码: 保证数据都是响应式的
    this.myState = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
 
    //  定义 muations
    let mutations = {}
    Object.keys(options.mutations).forEach(key => {
      mutations[key] = payload => {
        options.mutations[key](this.state, payload)
      }
    })
    //  提供commit 方法
    this.commit = (key, payload) => {
      mutations[key](payload)
    }
  }
 
  get state() {
    return this.myState.state
  }
}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      //判断 根 实例 有木有传入store 数据源,
      //如果传入了, 就把它放到实例的 $store上
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 2. 子组件去取父级组件的$store属性
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
 
export default {
  install,
  Store
}

vuex源码_第16张图片

  • 实现dispatch 方法 ,就跟 commit 差不多 了
// 收集 actions
let actions = {}
Object.keys(options.actions).forEach(key => {
  actions[key] = payload => {
    options.actions[key](this, payload)
  }
})
this.dispatch = (key, payload) => {
  actions[key](payload)
}
  • 整理代码如下:
let Vue
 
class Store {
  constructor(options = {}) {
    // 核心代码: 保证数据都是响应式的
    this.myState = new Vue({
      data() {
        return {
          state: options.state
        }
      }
    })
    //   定义实例上的 getters
    this.getters = {}
    //   遍历所有的对象中的方法名
    Object.keys(options.getters).forEach(key => {
      // 重新构造 this.getters 对象
      Object.defineProperty(this.getters, key, {
        get: () => {
          return options.getters[key](this.state)
        }
      })
    })
 
    //  定义 muations
    let mutations = {}
    Object.keys(options.mutations).forEach(key => {
      mutations[key] = payload => {
        options.mutations[key](this.state, payload)
      }
    })
    //  提供commit 方法
    this.commit = (key, payload) => {
      mutations[key](payload)
    }
    // 收集 actions
    let actions = {}
 
    Object.keys(options.actions).forEach(key => {
      actions[key] = payload => {
        options.actions[key](this, payload)
      }
    })
    this.dispatch = (key, payload) => {
      actions[key](payload)
    }
  }
 
  get state() {
    return this.myState.state
  }
}
 
const install = _Vue => {
  console.log("install")
  Vue = _Vue // 用一个变量接收 _Vue 构造器
  Vue.mixin({
    beforeCreate() {
      //判断 根 实例 有木有传入store 数据源,
      //如果传入了, 就把它放到实例的 $store上
      if (this.$options && this.$options.store) {
        this.$store = this.$options.store
      } else {
        // 2. 子组件去取父级组件的$store属性
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
 
export default {
  install,
  Store
}

参考:https://blog.csdn.net/qq_36407748/article/details/102778062

你可能感兴趣的:(vue,vue)