第一次尝试写本文,还是去年年底(2019-11-26),后来入职新公司忙的要死,也就鸽了。今天重新梳理,争取弄得清楚一些:带你读 Vuex 源码。2020-05-05 11:54:35

基础使用

Vuex是什么

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

  • 集中式存储
  • 状态以可预测的方式变化,改值得走流程使用提前写好的方法。方便追溯相关方法的调用来观察状态的流转过程。

用法

用法:

  • state
  • mutations
  • actions
  • getters
  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. const store = new Vuex.Store({
  5. state: {
  6. counter: 0
  7. },
  8. mutations: {
  9. add(state) {
  10. state.counter++
  11. }
  12. },
  13. actions: {
  14. add({commit}) {
  15. setTimeout(() => {
  16. commit('add')
  17. }, 1000);
  18. }
  19. },
  20. getters: {
  21. doubleCount(state){
  22. return state.count*2
  23. }
  24. }
  25. })
  26. new Vue({
  27. store
  28. })

在页面中:

  1. this.$store.commit('add'); // 同步
  2. this.$store.dispatch('add'); // 异步
  3. this.$store.state.counter; // state获取
  4. this.$store.getters.doubleCounter // getters

其他用法,写了很多都删了,具体看官方文档就好了。

image.png
如何阅读这张图片?

  • 我们再vue组件中调用 dispatch 使用Action来发起异步请求,得到的结果
  • Mutation 只接受同步函数,修改 State
  • 状态State的修改会引起组件的渲染render

合理使用vuex

源码基础实现

  • 实现一个 Store类,能够管理state,并且是响应式的,存在commit和dispatch方法
  • 封装成一个插件,方便用户使用use

    Core核心

既然是Vue生态里的插件,使用Vue.use(Vuex)new Vuex.Store({})来初始化插件,源码结构就需要实现 install方法,也需要导出 Store 。当然,因为vuex依赖vue,所以也是使用了 形参。

使用 Vue.mixin 方法,在 beforeCreate 时期混入vue

  1. // step1
  2. let Vue
  3. class Store {
  4. constructor(options) {}
  5. }
  6. // vue2里是这样使用的new Vuex.Store({})
  7. function install(_Vue) {
  8. Vue = _Vue
  9. Vue.mixin({
  10. // 筛选 判断 添加this.$store
  11. beforeCreate() {
  12. if (this.$options.store) {
  13. Vue.prototype.$store = this.$options.store
  14. }
  15. },
  16. })
  17. }
  18. // 导出 install 和 Store
  19. export default {
  20. install,
  21. Store,
  22. }

用户会传入 statemutationsactionsgettter等配置对象。

state

初始化时候会对

  1. store._vm=new Vue({
  2. data:{
  3. $$state: state
  4. },
  5. compouted
  6. })

所以访问 $store.state 实际上是访问的 this._vm._data.$$state

先来实现 state ,用户可以访问 state,但不能修改。

这里解决响应式,没有使用router中使用的util方法,而是直接使用了 new Vue

依赖promise

  1. // step2
  2. class Store{
  3. constructor(options){
  4. // 保证是响应式的,上次router使用了`Vue.util.defineReactive`
  5. // 这里直接使用了 Vue 构造函数来保证数据是响应式的
  6. // this._vm = new Vue({$$state=options.state})
  7. this.config = new Vue({
  8. data: {
  9. // state = options.state
  10. // 这里用了$$state 一个小技巧,不会挂载,只读,黑科技
  11. $$state = option.state
  12. }
  13. })
  14. }
  15. get state(){
  16. // return this._vm._data.$$state
  17. return this.config._data.$$state
  18. }
  19. set state(val){
  20. console.error('不能直接修改')
  21. }
  22. }

mutation

用户调用 this.$store.commit('add')

  1. class Store{
  2. constructor(options){
  3. // ... 略过
  4. // 找到方法集合
  5. this._mutations = options.mutations
  6. this.commit = this.commit.bind(this) // 时刻绑定this
  7. }
  8. commit(type,payload){
  9. // 要找到对应的方法
  10. const target = this._mutations[type]
  11. // 找不到怎么办
  12. if(!target){
  13. console.log('没找到')
  14. return;
  15. }
  16. target(this.state,payload)
  17. // mutations: {
  18. // add(state) {
  19. // state.counter++
  20. // }
  21. // }
  22. },
  23. }
  24. }

action

处理异步,和commit区别不大。

  1. class Store{
  2. constructor(options){
  3. this._actions = options.actions
  4. this.dispatch = this.dispatch.bind(this)
  5. }
  6. dispatch(type, payload) {
  7. const entry = this._actions[type]
  8. if (!entry) {
  9. console.error("未知action类型")
  10. return
  11. }
  12. // 把整个 this 放进去
  13. entry(this, payload)
  14. // actions: {
  15. // add({commit}) {
  16. // setTimeout(() => {
  17. // commit('add')
  18. // }, 1000);
  19. // }
  20. // }
  21. },
  22. }
  23. }

getter

getter是一个快捷选项,类似 computed

官方在 resetStoreVM 做了说明

  1. class Store{
  2. constructor(options){
  3. this._xGetter = options.getters
  4. this.xGetter = this.xGetter.bind(this)
  5. }
  6. xGetter(type){
  7. this.getters = {} // 定义对象
  8. const target = this._xGetter[type]
  9. }
  10. }
  1. class Store {
  2. constructor(opitons){
  3. this.xGetters(options.getters)
  4. }
  5. xGetters(types){
  6. this.getters={} // 定义了对象,然后遍历
  7. Object.keys(types).forEach(key=>{
  8. // 利用 define 进行映射
  9. Object.defineProperty(this.getters,key,{
  10. get:()=>{
  11. return types[key](this.state)
  12. }
  13. })
  14. })
  15. }
  16. }

wrapgetters

插件Plugin

下面这个函数会在每次Mutation 之后调用,这样也就实现了记录的作用。

  1. function actionLogPlugin(store){
  2. store.subscribe((mutation,state)=>{})
  3. }

如何实现 plugin?就比如大名鼎鼎的 vuex-persistedstate,原理就很简单,

每次 mutation变化都会把 state 存入 localStorage.setItem 里

  1. const myPlugin = store =>{
  2. store.subscribe((mutation,state)=>{})
  3. }
  • 本文部分参考 vue技术揭秘