Vuex的狀態(tài)管理——store

1.前言

狀態(tài)管理在開(kāi)發(fā)中已經(jīng)算是老生常談了,本篇文章我們轉(zhuǎn)向前端方向的Vue框架误褪,看看Vuex是怎么通過(guò)store處理數(shù)據(jù)狀態(tài)的管理的。由于Vue框架本身就是響應(yīng)式處理數(shù)據(jù)的栅组,所以store更多的為了跨路由去管理數(shù)據(jù)的狀態(tài)棕兼。在使用store之前,我們先來(lái)說(shuō)明store的兩個(gè)特性:

  • Vuex的狀態(tài)存儲(chǔ)是響應(yīng)式的圆存。當(dāng)Vue組件從store中讀取狀態(tài)的時(shí)候叼旋,若store中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新沦辙。
  • 你不能直接改變store中的狀態(tài)夫植。改變store中的狀態(tài)的唯一途徑就是顯式地提交(commit) mutation。這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化油讯,從而讓我們能夠?qū)崿F(xiàn)一些工具幫助我們更好地了解我們的應(yīng)用详民。

store是由五大成員組成的,開(kāi)發(fā)者可根據(jù)業(yè)務(wù)復(fù)雜程度撞羽,合理的使用它們阐斜。我們先來(lái)了解一下store的五大成員:

2.五大成員

  • StateVuexstore中的state是響應(yīng)式的,當(dāng)state中的數(shù)據(jù)發(fā)生變化時(shí)诀紊,所有依賴(lài)于該數(shù)據(jù)的組件都會(huì)自動(dòng)更新谒出。
  • GettersVuexstore中的getters可以理解為store的計(jì)算屬性,它們會(huì)基于store中的state派生出一些新的狀態(tài)邻奠,供組件使用笤喳。
  • MutationsVuexstore中的mutations是同步的事件操作,它們用于更改store中的state碌宴。在Vuex中杀狡,mutations是唯一可以修改store中的state的方式。
  • ActionsVuexstore中的actions是異步的事件操作贰镣,它們用于處理異步邏輯呜象,如API請(qǐng)求等膳凝。Actions可以通過(guò)提交mutations來(lái)修改store中的state
  • ModulesVuexstore可以通過(guò)模塊化的方式組織代碼恭陡,將一個(gè)大的store拆分成多個(gè)小的模塊蹬音,每個(gè)模塊都有自己的stategetters休玩、mutationsactions著淆。

其中StateMutations是構(gòu)成store必不可少的成員。那么store是怎么使用的呢拴疤?我們來(lái)看下一章節(jié)永部。

3.Store的使用

3.1.安裝:

npm install vuex

3.2.最簡(jiǎn)單的用法:

我們創(chuàng)建一個(gè)新的js文件,引入vuex并通過(guò)Vue.use()使用它呐矾,然創(chuàng)建一個(gè)Store對(duì)象并export

import Vuex from 'vuex'
import Vue from "vue";

Vue.use(Vuex)

const store =  new Vuex.Store({
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        count: 0
    },
    mutations: {
        add: function (state) {
            state.count++;
        }
    },
});

export default store

其中count是我們?cè)?code>state里聲明的需要被監(jiān)聽(tīng)的變量苔埋。add是個(gè)方法,其實(shí)現(xiàn)是count++凫佛。接下來(lái)我們?cè)?code>main.js里注入store實(shí)例:

import store from "./store"

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

好了我們的store已經(jīng)可以用了讲坎。我們假設(shè)有兩個(gè)路由:home pagenew page,我們看看store是怎么監(jiān)聽(tīng)數(shù)據(jù)變化并跨路由共享的愧薛。
home page代碼如下:

<template>
  <div>
    <div>I am home page</div>
    <div>{{ getCount() }}</div>
    <div @click="addCount" class="button">add count</div>
    <div @click="gotoNewPage" class="button">Go to newpage</div>
  </div>
</template>
  
  <script>
export default {
  methods: {
    gotoNewPage() {
      this.$router.push({ path: "/newpage" });
    },
    getCount() {
        return this.$store.state.count;
    },
    addCount() {
        this.$store.commit('add');
    },
  },
};
</script>
  
  <style lang="scss" scoped>
.button {
  width: 100px;
  height: 50px;
  background: #527dab;
  margin-top: 15px;
}
</style>
  

其中getCount()方法從storestate里讀取count的值晨炕。addCount()方法用來(lái)調(diào)用mutationsadd()方法,實(shí)現(xiàn)count++毫炉。們點(diǎn)擊“add count”按鈕看下效果:

IMG_2940.GIF

我們可以看到count的狀態(tài)變化會(huì)被監(jiān)聽(tīng)到瓮栗。那么我們跳轉(zhuǎn)至new page,代碼如下:

<template>
  <div>
    <div>new page</div>
    <div>{{ getCount() }}</div>
    <div @click="back" class="button">back</div>
  </div>
</template>
  
  <script>
export default {
  methods: {
    back() {
      this.$router.push({
        path: "/home",
      });
    },
    getCount() {
        return this.$store.state.count;
    },
  },
};
</script>
  
  <style lang="scss" scoped>
.button {
  width: 100px;
  height: 50px;
  background: #527dab;
  margin-top: 15px;
}
</style>

跨路由讀取到了count的值瞄勾。

IMG_2942.GIF

以上就是Store最簡(jiǎn)單的用法费奸,接下來(lái)我們進(jìn)階一下。

3.3.getters和actions

store增加gettersactions

const store = new Vuex.Store({
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        count: 0
    },
    getters: {
        getCount: function (state) {
            return "count是: " + state.count
        }
    },
    mutations: {
        add: function (state) {
            state.count++;
        }
    },
    actions: {
        addAction: function (context) {
            setTimeout(() => {
                context.commit('add')
            }, 500)

        }
    }
});

如果上一節(jié)所講进陡,getters類(lèi)似于計(jì)算屬性愿阐,有時(shí)候我們需要從store中的 state中派生出一些狀態(tài),那么就需要getters了趾疚。getters的返回值會(huì)根據(jù)它的依賴(lài)被緩存起來(lái)缨历,且只有當(dāng)它的依賴(lài)值發(fā)生了改變才會(huì)被重新計(jì)算。actions的核心在于可以包含任意異步操作糙麦。它提交的是mutation垂寥,不能直接修改state的值粗恢。addAction模擬了一個(gè)異步操作轩娶,500ms之后執(zhí)行add方法暗甥。我們接下來(lái)看一下使用,改造一下home pagegetCount()addCount()方法:

    getCount() {
      return this.$store.getters.getCount;
    },
    addCount() {
      this.$store.dispatch("addAction");
    },

actions需要使用store.dispatch()進(jìn)行分發(fā)焚廊,我們看一下執(zhí)行結(jié)果:

IMG_2944.GIF

點(diǎn)擊add count按鈕后500ms后冶匹,count加1习劫,通過(guò)getters得到新的顯示文本。

3.4.Modules

由于使用單一狀態(tài)樹(shù)徙硅,應(yīng)用的所有狀態(tài)會(huì)集中到一個(gè)比較大的對(duì)象榜聂。當(dāng)應(yīng)用變得非常復(fù)雜時(shí),store對(duì)象就有可能變得相當(dāng)臃腫嗓蘑。
為了解決以上問(wèn)題,Vuex允許我們將store分割成模塊(module)匿乃。每個(gè)模塊擁有自己的state桩皿、mutationaction幢炸、getter泄隔、甚至是嵌套子模塊。接著舉個(gè)例子:


const userStore = {
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        name: "tom"
    },
    getters: {
        getName: function (state) {
            return "name是: " + state.name
        }
    },
    mutations: {
        setName: function (state, name) {
            state.name = name;
        }
    },
    actions: {
        setNameAction: function (context,name) {
            console.log("setNameAction")
            setTimeout(() => {
                console.log("setName")
                context.commit('setName', name)
            }, 500)

        }
    }
};

export default userStore

剛才的實(shí)例代碼中宛徊,我們?cè)黾右粋€(gè)userStore佛嬉,為其配置了stategetters闸天,mutationsactions暖呕。然后我們?yōu)橹暗?code>store增加modules,代碼如下:

import Vuex from 'vuex'
import Vue from "vue";
import userModule from "@/store/modules/user"

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        count: 0
    },
    getters: {
        getCount: function (state) {
            return "count是: " + state.count
        }
    },
    mutations: {
        add: function (state) {
            state.count++;
        }
    },
    actions: {
        addAction: function (context) {
            setTimeout(() => {
                context.commit('add')
            }, 500)

        }
    },
    modules:{
        user: userModule
    }
});

export default store

導(dǎo)入了userModule苞氮,我們看看這個(gè)usermodule是怎么用的湾揽。

<template>
  <div>
    <div>I am home page</div>

    <div>{{ getName() }}</div>
    <div @click="setName" class="button">set Name</div>
  </div>
</template>
  
  <script>
export default {
  methods: {
    getName(){
        return this.$store.getters.getName; 
    },
    setName() {
        this.$store.dispatch("setNameAction","bigcatduan");
    },
  },
};
</script>
  
  <style lang="scss" scoped>
.button {
  width: 100px;
  height: 50px;
  background: #527dab;
  margin-top: 15px;
}
</style>

如果是從getters里取數(shù)據(jù)用法同之前的實(shí)例一樣。如果是從state里取數(shù)據(jù)則需要指定module

    getName(){
        return this.$store.state.user.name; 
    },

需要注意的是笼吟,如果module里的getter的方法名與父節(jié)點(diǎn)store的沖突了库物,則運(yùn)行的時(shí)候會(huì)取父節(jié)點(diǎn)store的值,但會(huì)報(bào)duplicate getter key的錯(cuò)誤贷帮。如果module里的getter的方法名與同節(jié)點(diǎn)的其它modules沖突了戚揭,則運(yùn)行的時(shí)候會(huì)根據(jù)在節(jié)點(diǎn)注冊(cè)的順序來(lái)取值,同時(shí)報(bào)duplicate getter key的錯(cuò)誤撵枢。比如:

    modules:{
        user: userModule,
        product: productModule
    }

由于userModule先被注冊(cè)到modules里民晒,所以取值時(shí)會(huì)取userModule的值。
如果父子之間或同節(jié)點(diǎn)的actionmutation的方法名相同的話(huà)诲侮,則各個(gè)節(jié)點(diǎn)同方法名的方法都會(huì)被執(zhí)行镀虐。
好了到此為止我們發(fā)現(xiàn)一個(gè)問(wèn)題,如果項(xiàng)目龐大沟绪,免不了會(huì)在不同的module里定義相同的變量或方法名稱(chēng)刮便,讓開(kāi)發(fā)者自己去維護(hù)龐大的方法/變量列表顯然不現(xiàn)實(shí),于是vuex引入了命名空間namespace绽慈。

3.5.命名空間

默認(rèn)情況下恨旱,模塊內(nèi)部的actionmutation仍然是注冊(cè)在全局命名空間的——這樣使得多個(gè)模塊能夠?qū)ν粋€(gè)actionmutation作出響應(yīng)辈毯。Getter同樣也默認(rèn)注冊(cè)在全局命名空間,但是目前這并非出于功能上的目的(僅僅是維持現(xiàn)狀來(lái)避免非兼容性變更)搜贤。必須注意谆沃,不要在不同的、無(wú)命名空間的模塊中定義兩個(gè)相同的getter從而導(dǎo)致錯(cuò)誤仪芒。
如果希望你的模塊具有更高的封裝度和復(fù)用性,你可以通過(guò)添加namespaced: true的方式使其成為帶命名空間的模塊掂名。當(dāng)模塊被注冊(cè)后据沈,它的所有getteractionmutation都會(huì)自動(dòng)根據(jù)模塊注冊(cè)的路徑調(diào)整命名饺蔑。
比如我們?yōu)閯偛诺?code>userModule設(shè)置namespaced: true锌介。


const userStore = {
    namespaced: true,
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        name: "tom"
    },
    getters: {
        getName: function (state) {
            return "name是: " + state.name
        }
    },
    mutations: {
        setName: function (state, name) {
            state.name = name;
        }
    },
    actions: {
        setNameAction: function (context,name) {
            console.log("setNameAction")
            setTimeout(() => {
                console.log("setName")
                context.commit('setName', name)
            }, 500)

        }
    }
};

export default userStore

setget的相應(yīng)方式變化如下:

    getName() {
      return this.$store.getters["user/getName"]; 
    },
    setName() {
      this.$store.dispatch("user/setNameAction", "bigcatduan"); 
    },

getterdispatchcommit時(shí)需要顯示的指明局部的命名空間猾警。
接下來(lái)我們思考一下孔祸,是否可以在不同的module下讀取到其它module的內(nèi)容呢?答案是可以的发皿。比如我們的store結(jié)構(gòu)如下:
根節(jié)點(diǎn):

import Vuex from 'vuex'
import Vue from "vue";
import userModule from "@/store/modules/user"
import productModule from "@/store/modules/product"

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        count: 20
    },
    getters: {
        getCount: function (state) {
            return "count是: " + state.count
        }
    },
    mutations: {
        add: function (state) {
            state.count++;
        }
    },
    actions: {
        addAction: function (context) {
            setTimeout(() => {
                context.commit('add')
            }, 500)

        }
    },
    modules:{
        user: userModule,
        product: productModule
    }
});

export default store

里面包含了userproduct這兩個(gè)module崔慧。
代碼如下:

//user

const userStore = {
    namespaced: true,
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        name: "tom"
    },
    getters: {
        getName: function (state, getters, rootState, rootGetters) {
            return "name是: " + state.name
        }
    },
    mutations: {
        setName: function (state, name) {
            state.name = name;
        }
    },
    actions: {
        setNameAction: function (context,name) {
            setTimeout(() => {
                context.commit('setName', name)
            }, 500)

        }
    }
};

export default userStore
//product

const productStore = {
    namespaced: true,
    state: {    //在state對(duì)象建立需要數(shù)據(jù)
        price: 15
    },
    getters: {
        getPrice: function (state) {
            return "price是: " + state.price
        }
    },
    mutations: {
        setPrice: function (state, price) {
            state.price = price;
        }
    },
    actions: {
        setPriceAction: function (context,price) {
            setTimeout(() => {
                context.commit('setPrice', price)
            }, 500)

        }
    }
};

export default productStore

我們改造一下usergetters

//user
    getters: {
        getName: function (state, getters, rootState, rootGetters) {
            console.log("rootState count: ",rootState.count)
            console.log("product state price: ",rootState.product.price)
            console.log("root getter getCount: ",rootGetters.getCount)
            console.log("product getter getPrice: ",rootGetters["product/getPrice"])
            return "name是: " + state.name
        }
    },

除了statevuex還為我們提供了getters雳窟,rootStaterootGetters這幾個(gè)參數(shù)尊浪。于是我們可以讀取根節(jié)點(diǎn)state相應(yīng)的變量,其他節(jié)點(diǎn)state相應(yīng)的變量封救,根getters里的方法拇涤,還可以找到其他getters里的方法。上面代碼的打印結(jié)果如下:

截屏2023-04-03 11.11.34.png

我們?cè)賮?lái)改造一下actions

    actions: {
        setNameActionRoot: {
            root:true,
            handler (namespacedContext, name){
                console.log("setNameAction")
                setTimeout(() => {
                    console.log("setName")
                    namespacedContext.commit('setName', name)
                }, 500)
            }

        },
    }

root:true意味著我們?yōu)檫@個(gè)帶命名空間的模塊注冊(cè)全局action誉结,所以我們依然可以不指定命名空間執(zhí)行dispatch方法:

this.$store.dispatch("setNameActionRoot", "bigcatduan");

繼續(xù)改造一下actions

    actions: {
        setNameAction: function (context,name) {
            setTimeout(() => {
                context.commit('setName', name)
            }, 500)

        },
        setSomeActions ( { dispatch, commit, getters, rootGetters },someAction){
            console.log("root getter getCount: ",rootGetters.getCount)
            console.log("product getter getPrice: ",rootGetters["product/getPrice"])
            dispatch('setNameAction',someAction.name)
            commit('setName',someAction.name)
            commit('product/setPrice', someAction.price, {root:true})
        }
    }

可以拿到全局rootGetters鹅士。若需要在全局命名空間內(nèi)分發(fā)action 或提交 mutation,將{ root: true } 作為第三參數(shù)傳給dispatchcommit即可惩坑。
好了以上就是store的用法掉盅,更多用法大家可以繼續(xù)探索。接下來(lái)我們來(lái)講一下store的實(shí)現(xiàn)原理以舒。

4.實(shí)現(xiàn)原理

我們先來(lái)看看構(gòu)造方法:

//Store
  constructor (options = {}) {
    if (__DEV__) {
      assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
      assert(this instanceof Store, `store must be called with the new operator.`)
    }

    const {
      plugins = [],
      strict = false,
      devtools
    } = options

    // store internal state
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._makeLocalGettersCache = Object.create(null)

    // EffectScope instance. when registering new getters, we wrap them inside
    // EffectScope so that getters (computed) would not be destroyed on
    // component unmount.
    this._scope = null

    this._devtools = devtools

    // bind commit and dispatch to self
    const store = this
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch (type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit (type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // init root module.
    // this also recursively registers all sub-modules
    // and collects all module getters inside this._wrappedGetters
    installModule(this, state, [], this._modules.root)

    // initialize the store state, which is responsible for the reactivity
    // (also registers _wrappedGetters as computed properties)
    resetStoreState(this, state)

    // apply plugins
    plugins.forEach(plugin => plugin(this))
  }

創(chuàng)建了各個(gè)成員變量趾痘,其中最重要的是_modules的創(chuàng)建。之后依次執(zhí)行installModule()蔓钟,resetStoreState()plugins.forEach(plugin => plugin(this))永票。我們先來(lái)看看_modules的創(chuàng)建。

4.1. ModuleCollection

_modules對(duì)所有的module進(jìn)行了初始化并構(gòu)造了其依賴(lài)關(guān)系。其構(gòu)造方法實(shí)現(xiàn)如下:

//ModuleCollection
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

調(diào)用了register方法:

  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

在初始化的時(shí)候侣集,由于傳入的是根module键俱,path.length === 0,所以會(huì)創(chuàng)建一個(gè)Module對(duì)象世分,并為root賦值编振,把它設(shè)置為根moduleModule的構(gòu)造方法如下:

//Module
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }

回到ModuleCollection臭埋,如果存在子modules踪央,就會(huì)遍歷modules,遞歸執(zhí)行register()方法斋泄。而再次執(zhí)行register()方法時(shí)杯瞻,path.length === 0false,會(huì)找到它的parent炫掐,并執(zhí)行addChild()方法:

//Module
  addChild (key, module) {
    this._children[key] = module
  }

由此實(shí)現(xiàn)了modules依賴(lài)關(guān)系的創(chuàng)建,生成了一個(gè)modules樹(shù)睬涧。接下來(lái)我們看installModule()方法的實(shí)現(xiàn):

4.2.installModule()

它的作用是對(duì)根module進(jìn)行初始化募胃,根據(jù)上一章節(jié)生成的modules樹(shù),遞歸注冊(cè)每一個(gè)module畦浓。代碼如下:

export function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      parentState[moduleName] = module.state
    })
  }

  const local = module.context = makeLocalContext(store, namespace, path)

  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

如果設(shè)置了nameSpace痹束,則向_modulesNamespaceMap注冊(cè)。之后如果不是根module讶请,將模塊的state添加到state鏈中祷嘶,就可以按照state.moduleName進(jìn)行訪(fǎng)問(wèn)。
接下來(lái)創(chuàng)建makeLocalContext上下文夺溢,為該module設(shè)置局部的dispatch论巍、commit方法以及gettersstate,為的是在局部的模塊內(nèi)調(diào)用模塊定義的actionmutation风响,這個(gè)過(guò)程具體就不展開(kāi)了嘉汰。
接下來(lái)分別遍歷_mutations_actions_wrappedGetters進(jìn)行注冊(cè)状勤。注冊(cè)過(guò)程大同小異鞋怀,我們?nèi)?code>_mutations的注冊(cè)來(lái)看看:

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

把對(duì)應(yīng)的值函數(shù)封裝后存儲(chǔ)在數(shù)組里面,然后作為store._mutations的屬性持搜。store._mutations收集了我們傳入的所有mutation函數(shù)密似。
installModule()的分析就完成了,我們繼續(xù)看resetStoreState()的實(shí)現(xiàn)葫盼。

4.4.resetStoreState()

這個(gè)方法的作用是初始化state残腌,并且注冊(cè)getters作為一個(gè)computed屬性。

export function resetStoreState (store, state, hot) {
  const oldState = store._state
  const oldScope = store._scope

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computedObj = {}
  const computedCache = {}

  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  const scope = effectScope(true)

  scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      computedObj[key] = partial(fn, store)
      computedCache[key] = computed(() => computedObj[key]())
      Object.defineProperty(store.getters, key, {
        get: () => computedCache[key].value,
        enumerable: true // for local getters
      })
    })
  })

  store._state = reactive({
    data: state
  })
//...
}

遍歷store._wrappedGetters,并新建computed對(duì)象進(jìn)行存儲(chǔ)废累,通過(guò)Object.defineProperty方法為getters對(duì)象建立屬性并實(shí)現(xiàn)響應(yīng)式邓梅,使得我們通過(guò)this.$store.getters.xxxgetter能夠訪(fǎng)問(wèn)到該getters。最后利用reactivestate實(shí)現(xiàn)響應(yīng)式邑滨。

4.5.install

vue里日缨,通過(guò)Vue.use()掛載全局變量,所以業(yè)務(wù)組件中通過(guò)this就能訪(fǎng)問(wèn)到store

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this

    const useDevtools = this._devtools !== undefined
      ? this._devtools
      : __DEV__ || __VUE_PROD_DEVTOOLS__

    if (useDevtools) {
      addDevtools(app, this)
    }
  }

4.6.commit和dispatch

最后我們?cè)賮?lái)看看commit和dispatch都做了什么

//commit
  commit (_type, _payload, _options) {
    // check object-style commit
    const {
      type,
      payload,
      options
    } = unifyObjectStyle(_type, _payload, _options)

    const mutation = { type, payload }
    const entry = this._mutations[type]
//...
    this._withCommit(() => {
      entry.forEach(function commitIterator (handler) {
        handler(payload)
      })
    })

    this._subscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .forEach(sub => sub(mutation, this.state))
//...
  }

主要做了兩件事:1.遍歷_mutations掖看,執(zhí)行handler匣距。2.遍歷_subscribers,通知訂閱者哎壳。我們?cè)诳?code>dispatch

  dispatch (_type, _payload) {
    // check object-style dispatch
    const {
      type,
      payload
    } = unifyObjectStyle(_type, _payload)

    const action = { type, payload }
    const entry = this._actions[type]
//...

    const result = entry.length > 1
      ? Promise.all(entry.map(handler => handler(payload)))
      : entry[0](payload)

    return new Promise((resolve, reject) => {
      result.then(res => {
        try {
          this._actionSubscribers
            .filter(sub => sub.after)
            .forEach(sub => sub.after(action, this.state))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in after action subscribers: `)
            console.error(e)
          }
        }
        resolve(res)
      }, error => {
        try {
          this._actionSubscribers
            .filter(sub => sub.error)
            .forEach(sub => sub.error(action, this.state, error))
        } catch (e) {
          if (__DEV__) {
            console.warn(`[vuex] error in error action subscribers: `)
            console.error(e)
          }
        }
        reject(error)
      })
    })
  }

dispatch使用Promise封裝了異步操作毅待,遍歷_actions執(zhí)行handler操作,并遍歷_actionSubscribers通知訂閱者归榕。

5.總結(jié)

Vuex的狀態(tài)管理方式store的使用和原理就介紹到這里尸红。最后截取一張官網(wǎng)的圖為大家進(jìn)行一下總結(jié)。

截屏2023-04-03 17.54.19.png

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刹泄,一起剝皮案震驚了整個(gè)濱河市外里,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌特石,老刑警劉巖盅蝗,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異姆蘸,居然都是意外死亡墩莫,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)逞敷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狂秦,“玉大人,你說(shuō)我怎么就攤上這事兰粉」嗜” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵玖姑,是天一觀的道長(zhǎng)愕秫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)焰络,這世上最難降的妖魔是什么戴甩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮闪彼,結(jié)果婚禮上甜孤,老公的妹妹穿的比我還像新娘协饲。我一直安慰自己,他們只是感情好缴川,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布茉稠。 她就那樣靜靜地躺著,像睡著了一般把夸。 火紅的嫁衣襯著肌膚如雪而线。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天恋日,我揣著相機(jī)與錄音膀篮,去河邊找鬼。 笑死岂膳,一個(gè)胖子當(dāng)著我的面吹牛誓竿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谈截,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼筷屡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了簸喂?” 一聲冷哼從身側(cè)響起速蕊,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娘赴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跟啤,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诽表,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隅肥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竿奏。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腥放,靈堂內(nèi)的尸體忽然破棺而出泛啸,到底是詐尸還是另有隱情,我是刑警寧澤秃症,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布候址,位于F島的核電站,受9級(jí)特大地震影響种柑,放射性物質(zhì)發(fā)生泄漏岗仑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一聚请、第九天 我趴在偏房一處隱蔽的房頂上張望荠雕。 院中可真熱鬧,春花似錦、人聲如沸炸卑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盖文。三九已至嘱蛋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間椅寺,已是汗流浹背浑槽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留返帕,地道東北人桐玻。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像荆萤,于是被迫代替她去往敵國(guó)和親镊靴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容