手寫Vuex源碼


Vuex原理解析

Vuex是基於Vue的響應式原理基礎,所以無法拿出來單獨使用,必須在Vue的基礎之上使用。

 

1.Vuex使用相關解析

main.js

 
1 import store form './store' // 引入一個store文件
2 3  new Vue({
4      // 在Vue初始化的過程中,注入一個store屬性,內部會將這個屬性放到每個組件的$store上
5      store, 
6  })

 

 

store.js

 
 1 import Vuex from 'Vuex'
 2  3  Vue.use(Vuex) 
 4  5  // 通過Vuex中的一個屬性 Store 創建一個store的實例
 6  export default new Vuex.Store({
 7      state: { // 單一數據源
 8          age: 10
 9      },
10      mutations: { // 
11          // payload 載荷 
12          syncChange(state,payload) { // 修改狀態的方法 同步更改
13              state.age+= payload
14          }
15      },
16      actions: {
17          asyncChange({commit}, payload) {
18              setTimeout(() => {
19                  commit('syncChange',payload)
20              },1000)
21          }
22      }
23  })
24  //mutations中增加異步操作 嚴格模式下會直接報錯,普通模式下不會報錯但不合法

 

 

2.Vuex原理解析實現

首先我們要清楚Vuex的定位,它是一個插件。且必須基於之上Vue來使用,為什么這么說呢,因為他的數據響應是基於Vue的。

 

1.Vuex核心概念

state 驅動應用的數據源。

Getter getter的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生變化了改變才會被重新計算(由此你是不是想到了計算屬性,對完全可以這么理解)。

Mutation 進行Vuex中store狀態的更改,也是官方規定更改狀態的唯一途徑。

Action 進行異步操作的場所,但是更改數據還是需要commit提交。

Module 單一狀態樹對象比較復雜,Vuex允許我們將Store分割成多模塊,每個模塊擁有自己獨立的內容。

 

2.實現Vuex

store.js

先創建一個入口文件

 
 1 import Vue from 'vue'
 2  // import Vuex from 'vuex' 官方的Vuex插件
 3  import Vuex from './vuex/index1' // 自己寫的Vuex
 4  5  Vue.use(Vuex) // 默認會執行當前插件的install方法
 6  7  // 通過Vuex中的一個屬性 Store 創建一個store的實例
 8  export default new Vuex.Store({
 9    // 定義數據
10    modules: {
11      a: {
12        state: {
13          age: 'a100'
14        },
15        mutations: {
16          syncChange() {
17            console.log('a');
18          }
19        },
20      },
21      b: {
22        state: {
23          age: 'b100'
24        },
25        mutations: {
26          syncChange() {
27            console.log('b');
28          }
29        },
30        modules: {
31          c: {
32            state: {
33              age: 'c100'
34            },
35            mutations: {
36              syncChange() {
37                console.log('c');
38              }
39            },
40          }
41        }
42      }
43    },
44    state: {
45      age: 10
46    },
47    mutations: {
48      syncChange(state, payload) {
49        state.age += payload
50      }
51    },
52    actions: {
53      asyncChange({ commit }, payload) {
54        setTimeout(() => {
55          commit('syncChange', payload)
56        }, 1000)
57      }
58    },
59    getters: {
60      myAge(state) {
61        return state.age + 20
62      }
63    }
64  })

 

 

index1.js

這邊會暴露出一個install方法,Vue.sue()的時候會調用它。還有一個提供實例化的Store類

 
  1 let vue
  2  const install = (_Vue) => {
  3      Vue = _Vue // install方法調用時,會將Vue作為參數傳入
  4      
  5      Vue.mixin({ // 全局注冊一個混入,影響注冊以后的每一個創建的Vue實例。
  6          beforeCreate() {
  7          // 判斷當前根實例上有沒有store,有的話把根組件的的store屬性 放到每個組件的實例上
  8          // 這樣每個組件上都能直接實現this.$store去訪問store里面的東西
  9              if(this.$option.store) { 
 10                  this.$store = this.$options.store
 11              } else {
 12                  this.$store = this.$parent && this.$parent.$store
 13              }
 14          }
 15      })
 16  }
 17  18  19  class Store { // 用戶獲取的是這個store類的實例
 20      constructor(options) {
 21          // 創建Vue的實例 保證更新狀態可以刷新視圖
 22          this.vm = new Vue({
 23              data: {
 24                  state: optons.state
 25              }
 26          })
 27      }
 28      
 29      // es6 的訪問器
 30      get state() {
 31          return this.vm.state
 32      }
 33      
 34      this.getters = {}
 35      this.mutations = {}
 36      this.actions = {}
 37      // 1、需要將用戶傳入的數據進行格式化操作
 38      this.moudules = new ModulesCollections(options)
 39  40      // 2、遞歸的安裝模塊
 41      installModule(this,this.state,[], this.modules.root)
 42  43      // 調用
 44      commit = (mutationName, payload) => {
 45          // es7寫法 這個里面的this 永遠指向當前的store實例
 46          this.mutaions[mutationName].forEach(fn =>fn(payload))
 47      }
 48      
 49      dispath = (actionName, payload) => {
 50          this.actions[actionName].forEach(fn =>fn(payload))
 51      }
 52  }
 53  54  // 定義一個forEach遍歷當前對象屬性然后執行一個回調函數,后面要用到
 55  const forEach = (obj, callback) => {
 56      Object.keys(obj).forEach(key => {
 57          callback(key, obj[key])
 58      })
 59  }
 60  61  // 格式化用戶數據
 62  class ModuleCollection {
 63      constructor(options) {
 64          // 深度將所有的子模塊都遍歷一遍
 65          this.register([], ooptions)
 66      }
 67      register(path, rootModule) {
 68          let rawModule = {
 69              _raw: rootModule,
 70              _children: {},
 71              state: rootModule.state
 72          }
 73          if(!this.root) {
 74              this.root = rawModule
 75          } else {
 76              // 找到要定義的模塊,將這個模塊定義他父親的_children屬性里
 77              let parentModule = path.slice(0,-1).reduce((root, current) => {
 78                  return root._children[current]
 79              }, this.root)
 80              parentModule._childen[path[path.length - 1]] = rawModule
 81          }
 82          
 83          // 如果有子模塊
 84          if(rootModule.modules) {
 85              forEach(rootModule.modules,(moduleName, module) => {
 86                  this.register(path.concat(moduleName), module)
 87              })
 88          }
 89      }
 90  }
 91  92  // 遞歸安裝模塊
 93  function installModule(store, rootState, path, rawModeule) {
 94      // 如果有子模塊,安裝子模塊的狀態
 95      if(path.length > 0) {
 96          let parentState = path.slice(0,-1).reduce((root, current) => {
 97              return root[current]
 98          }, rootState)
 99           Vue.set(parentState, path[path.length -1],rawModule.state)
100      }
101      
102      let getters = rawModule._raw.getters // 取用戶的getter
103      if(getters) {
104          forEach(getters, (getterName, value) => {
105              Object.defineProperty(store.getters, getterName, {
106                  get: () => {
107                      return value(rawModule.state)
108                  }
109              })
110          })
111      }
112      
113      let mutations = rawModule.raw.mutations // 取用戶的mutation
114      if(mutations) {
115          forEach(mutations, (mutationName, value) => {
116         let arr = store.mutations[mutationName] || (store.mutaons[mutationName] = [])
117          })
118          arr.push((plyload) => {
119              value(rawModule.state, payload)
120          })
121      }
122      
123      let actions = rawModule._raw.actions // 取用戶的action
124      if(actions) {
125          forEach(actions, (actionName, value) => {
126              let arr = store.actions[actionName] || (store.actions[actionName] = [])
127              arr.push((payload) => {
128                  value(store, payload)
129              })
130          })
131      }
132      
133      // 遞歸
134      forEach(rawModule._childen, (moduleName, rawModule) => {
135          installModule(store, rootState, path.concat(moduleName),rawModule)
136      })
137  }

 

 

3.實現步驟總結:

1、作為插件引入,執行install方法調用Vue.mixin在Vue全局生命周期混入一個方法,將Vuex中定義的數據源掛載到this.$store,即當前組件的實例上。

2、state 直接new Vue實例,將數據源傳入。完成數據源響應式操作。

3、getters 遞歸遍歷用戶傳入的getters對象,拿到每個里面每一個函數,通過Object.definedProperty屬性處理。當get函數讀取compile,觸發get調用相應函數(函數內部自動傳入當前數據源state作為參數),完成數據響應。

4、mutations 遞歸遍歷用戶傳入的mutations 對象,將相同名稱下的函數都掛載到當前實例的mutations數組中,完成訂閱。commit的時候拿到對應的函數名稱進行遍歷mutations數組調用對應名稱函數,完成發布。

5、actiosns 操作和mutations一樣。

6、module 是將用戶傳入的數據進行格式化,格式化好以后執行上面的安裝模塊的方法。具體查看上方installModule方法的詳細操作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM