Vuex原理淺析

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。vue

狀態管理

一般咱們能夠這樣來使用vuex

new Vue({
  // state
  data () {
    return {
      count: 0
    }
  },
  // view
  template: ` <div>{{ count }}</div> `,
  // actions
  methods: {
    increment () {
      this.count++
    }
  }
})
複製代碼

這個狀態自管理應用包含如下幾個部分:數據結構

  • state,驅動應用的數據源;
  • view,以聲明方式將 state 映射到視圖;
  • actions,響應在 view 上的用戶輸入致使的狀態變化。`

vuex初始化

vuex的核心就是定義了一個store(容器)來管理應用的的數據狀態,與單純的全局對象數據存儲相比,vuex的功能更強大。異步

  • Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地獲得高效更新。
  • 不能直接改變 store 中的狀態。改變 store 中的狀態的惟一途徑就是顯式地提交 (commit) mutation。這樣使得咱們能夠方便地跟蹤每個狀態的變化。 **在項目中使用vuex**相信下面的代碼,用vue開發過項目的人都很熟悉
import Vue from 'vue'
import Vuex from 'vuex'
// 這裏會執行vuex的install方法 
Vue.use(Vuex);

export default new Vuex.store({
    state: { // 統一的狀態管理
    age:10,
    a:100
  },
  getters:{
    // 計算屬性
    myAge(state){ // object.defineProperty
      return state.age + 18;
    }
  },
  mutations: { // 能夠更改狀態
    syncAdd(state,payload){ // $store.commit()
      state.age += payload;
    },
    syncMinus(state,payload){
      state.age -= payload

    }
  },
  actions: { // 異步提交更改
    asyncMinus({commit},payload){ // action 異步獲取完後 提交到mutation中
      setTimeout(()=>{
        commit('syncMinus',payload);
      },1000)
    }
  }
})

複製代碼

這樣咱們就能夠在項目中集中管理公用的狀態了。那麼vuex是怎麼實現這一切的呢?vuex的入口提供的install方法就能夠完成初始化工做async

const install = (_vue) => {
   //vue.use 傳入vue的構造函數
    Vue = _vue;
    // 給vue的組件實例都混入一個鉤子函數
    Vue.mixin({
        beforeCreate() {
            // 首先 store是放在根組件上,咱們須要獲取,並給每一個組件掛上 即 this.$store = store
            if(this.$options && this.$options.store) {
                // 給根實例增長$store屬性
                this.$store = this.$options.store
            }else{
                // 其餘組件只需從它的父級獲取
                this.$store = this.$parent && this.$parent.$store
            }
        }
    })
}
// store類
class Store{
    
}

export default {
    install,
    Store
}
複製代碼

核心store

vuex的初始化工做很簡單,咱們在 import Vuex 以後,會實例化其中的 Store 對象,返回 store 實例並傳入 new Vue 的 options 中,也就是咱們剛纔提到的 options.store。Store 對象的構造函數接收一個對象參數,它包含 actions、getters、state、mutations、modules 等 Vuex 的核心概念。函數

// 迭代對象的 會將對象的 key 和value 拿到
const forEach = (obj,cb)=>{ 
    Object.keys(obj).forEach(key=>{
        cb(key,obj[key]);
    })
}
class Store {
    constructor(options={}) {
         // 將用戶的狀態放到了store中 定義了響應式變化 數據更新 更新視圖
        this.s = new Vue({
            data() {
                return {
                    state: options.state
                }
            }
        })
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        // 計算屬性
        forEach(options.getters,(getterName,fn)=>{
            Object.defineProperty(this.getters,getterName,{
                get() {
                    return fn(this.state)
                }
            })
        })
        //mutations
        forEach(options.mutations,(mutationName,fn)=>{
            this.mutations[mutationName] = (payload)=>{
                // 內部的第一個參數是狀態
                fn(this.state,payload)
            }
        })
        //actions
        forEach(options.actions,(actionName,fn)=>{
            this.actions[actionName] = (payload)=>{
                fn(this,payload)
            }
        })
    }
    get state(){ // 類的屬性訪問器
        return this.s.state
    }
    //提交更改 找到對應的mutations函數執行
    commit = (mutationName,payload)=>{
        this.mutations[mutationName](payload)
    }
    dispatch = (actionName,payload)=>{
        this.actions[actionName](payload)
    }
}
複製代碼

到此,一個簡陋的vuex狀態管理就實現了,可是把全部的狀態都放到store存儲,那麼這個對象會變的十分的龐大,爲了解決以上問題,Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割:ui

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
複製代碼

vuex的模塊是默認是沒有強制命名空間的,除了state按模塊區分了,若是模塊中的matutions和actions和主對象有同名,那麼他們會一塊兒執行。this

class Store {
    constructor(options = {}){
         this.s = new Vue({ 
            data(){
                return {state:options.state}
            }
        });
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        // 模塊把數據格式化成一個有層次的樹狀結構,方面後面解析
        this._modules = new ModuleCollection(options); 
    }
    get state() {
        return this.s.state
    }
}
複製代碼

從數據結構上來看,模塊的設計就是一個樹型結構,store 自己能夠理解爲一個 root module,它下面的 modules 就是子模塊,Vuex 須要完成這顆樹的構建spa

// 模塊的數據結構
let root = {
    _raw:options,
    _chidlren:{
        a:{
            _raw:{},
            _chidlren:{},
            state:{a:1}
        },
        b:{}
    },
    state:options.state
}
複製代碼
class ModuleCollection {
    constructor(options) {
        this.register([], options); // 註冊模塊 將模塊註冊成樹結構
    }
    register(path,rootModule) {
        let module = { // 將模塊格式化
            _rawModule: rootModule,
            _chidlren: {},
            state: rootModule.state
        }
        if(path.length ==0 ) {
            // 若是是根模塊 將這個模塊掛在到根實例上
            this.root = module;
        }else {
            // 若是是modules 裏面的內容
            // _children 屬性 找到上一級路徑
            let parent = path.slice(0,-1).reduce((root,current)=>{
                return root._children[current] 
            },this.root)
            parent._children[path[path.length - 1]] = module
        }
        //看當前模塊是否有modules 
        if(rootModule.modules) {
            forEach(rootModule.modules,(moduleName, module) => {
                this.register(path.concat(moduleName),module)
            })
        }
    }
}
複製代碼

ModuleCollection 實例化的過程就是執行了 register 方法,在源碼中register有3個參數,其中 path 表示路徑,由於咱們總體目標是要構建一顆模塊樹,path 是在構建樹的過程當中維護的路徑;rawModule 表示定義模塊的原始配置,另外一個是runtime表示是不是一個運行時建立的模塊,這裏不影響。設計

初始化模塊後,咱們就有了一個模塊的樹狀結構,咱們就能夠對對模塊中的 state、getters、mutations、actions 作初始化工做,把它們和store對象鏈接起來。

/* rootState:表示根上面的狀態 path: 模塊路徑 rootMudule: 當前模塊對象內容 */
const installModule = (store, rootState, path, rootModule) => {
    if(path.length > 0) {
        let parent = path.slice(0,-1).reduce((root,currnet)=>{
            return root[current]
        },rootSate)
        // 找到模塊對應得狀態,按路徑 掛在 store.state
        Vue.set(parent,path[path.lenght-1],rootModule.state)
    }
    // 處理 模塊中 getters actions mutation
    let getters = rootModule._rawModule.getters;
    if(getters) {
        forEach(getters,(getterName,fn)=>{
            Object.defineProperty(store.getters,getterName,{
                get() {
                    // 讓getter執行當本身的狀態 傳入
                    return fn(rootModule.state); // 讓對應的函數執行
                }
            })
        })
    }
    let mutations = rootModule._rawModule.mutations;
    if (mutations) {
        forEach(mutations, (mutationName, fn) => {
            let mutations = store.mutations[mutationName] || [];
            // 默認狀況下,沒有強制命名空間,因此 同名得mutations 會一塊兒執行
            mutations.push((payload) => {
                fn(rootModule.state, payload);
            })
            store.mutations[mutationName] = mutations;
        })
    }
    let actions = rootModule._rawModule.actions;
    if (actions) {
        forEach(actions, (actionName, fn) => {
            let actions = store.actions[actionName] || [];
            actions.push((payload) => {
                fn(store, payload);
            })
            store.actions[actionName] = actions;
        })
    }
    // 處理_children裏面得module
    forEach(rootModule._chidlren,(moduleName, module) => {
        installModule(store, rootState, path.concat(moduleName), module)
    })
}
複製代碼

有了installModule這個處理模塊數據得方法後,咱們再改下Store的代碼:

class Store {
    constructor(options = {}) {
        // 將用戶的狀態放到了store中
        this.s = new Vue({ // 核心 定義了響應式變化 數據更新 更新視圖
            data() {
                return { state: options.state }
            }
        }); // 用來維護全局數據的
        this.getters = {};
        this.mutations = {};
        this.actions = {};
        this._subscribes = [];
        this._modules = new ModuleCollection(options); // 把數據格式化成一個 想要的樹結構
        // this._modules.root 從根模塊開始安裝
        installModule(this, this.state, [], this._modules.root);

    }
    // 提交更改 會在當前的 store上 找到對應的函數執行
    commit = (mutationName, payload) => { // 保證this
        this.mutations[mutationName].forEach(fn => fn(payload))
    }
    dispatch = (actionName, payload) => {
        this.actions[actionName](payload); //源碼裏有一個變量 來控制是不是經過mutation 來更新狀態的
    }
    get state() { // 類的屬性訪問器
        return this.s.state
    }
}
複製代碼

以上就是一個簡易vuex的完整代碼了,有錯誤的地方請你們指正。

相關文章
相關標籤/搜索