<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【W(wǎng)eb技術(shù)】1169- 從 Vuex 學習狀態(tài)管理

          共 8110字,需瀏覽 17分鐘

           ·

          2021-12-14 19:39


          眾所周知,Vuex[2] 是 Vue 官方的狀態(tài)管理方案。

          Vuex 的用法和 API 不難,官網(wǎng)介紹也簡潔明了。得益于此,將 Vuex 快速集成到項目里非常容易。然而正因為用法靈活,很多同學在 Vuex 的設(shè)計和使用上反而有些混亂。

          其實在使用前,我們不妨暫停一下,思考幾個問題:

          • 什么是狀態(tài)管理?
          • 我為什么要用 Vuex?
          • 組件內(nèi)部狀態(tài)和 Vuex 狀態(tài)如何分配?
          • 使用 Vuex 會有哪些潛在問題?

          如果你對這些問題模棱兩可,那么恭喜你,這篇文章可能是你需要的。

          下面請和我一起,從起源開始,以 Vuex 為例,共同揭開狀態(tài)管理的神秘面紗。

          大綱預(yù)覽

          本文介紹的內(nèi)容包括以下方面:

          • 狀態(tài)與組件的誕生
          • 需要狀態(tài)管理嗎?
          • 單一數(shù)據(jù)源
          • 狀態(tài)更新方式
          • 異步更新?
          • 狀態(tài)模塊化
          • 模塊化的槽點
          • 下一步

          狀態(tài)與組件的誕生

          自三大框架誕生起,它們共有的兩個能力徹底暴擊了 Jquery。這兩個能力分別是:

          1. 數(shù)據(jù)驅(qū)動視圖
          2. 組件化

          數(shù)據(jù)驅(qū)動視圖,使我們告別了只能依靠操作 DOM 更新頁面的時代。我們不再需要每次更新頁面時,通過層層 find 找到 DOM 然后修改它的屬性和內(nèi)容,可以通過操作數(shù)據(jù)來實現(xiàn)這些事情。

          當然了在我們前端的眼里,數(shù)據(jù)基本可以理解為存儲各種數(shù)據(jù)類型的 變量。在 數(shù)據(jù)驅(qū)動 這個概念出現(xiàn)之后,一部分變量也被賦予了特殊的含義。

          首先是普通變量,和 JQ 時代沒差,僅用來存儲數(shù)據(jù)。除此之外還有一類變量,它們有響應(yīng)式的作用,這些變量與視圖綁定,當變量改變時,綁定了這些變量的視圖也會觸發(fā)對應(yīng)的更新,這類變量我稱之為狀態(tài)變量。

          所謂數(shù)據(jù)驅(qū)動視圖,嚴格說就是狀態(tài)變量在驅(qū)動視圖。隨著 Vue,React 的大力普及之下,前端開發(fā)們的工作重心逐漸從操作 DOM 轉(zhuǎn)移到了操作數(shù)據(jù),狀態(tài)變量成為了核心。

          狀態(tài)變量,現(xiàn)在大家似乎更愿意稱之為狀態(tài)。我們經(jīng)常詞不離口的狀態(tài),狀態(tài)管理,其實這個狀態(tài)就是指狀態(tài)變量。下文提到的狀態(tài)同樣也是指狀態(tài)變量。

          有了狀態(tài)之后,組件也來了。

          JQ 時代的前端一個頁面就是一個 html,沒有“組件”的概念,對于頁面中的公共部分,想要優(yōu)雅的實現(xiàn)復(fù)用簡直不要太難。所幸三大框架帶來了非常成熟的組件設(shè)計,可以很容易的抽取一個 DOM 片段作為組件,而且組件內(nèi)部可以維護自己的狀態(tài),獨立性更高。

          組件的一個重要特性,就是內(nèi)部的這些狀態(tài)是對外隔離的。父組件無法訪問到子組件內(nèi)部的狀態(tài),但是子組件可以訪問父組件顯示傳過來的狀態(tài)(Props),并且根據(jù)變化自動響應(yīng)。

          這個特性可以理解為狀態(tài)被模塊化了。這樣的好處是,不需要考慮當前設(shè)置的狀態(tài)會影響到其他組件。當然了組件狀態(tài)徹底隔離也是不現(xiàn)實的,必然會有多個組件共享狀態(tài)的需求,這種情況的方案就是將狀態(tài)提取到離這些組件最近的父組件,通過 Props 向下傳遞。

          上述共享狀態(tài)的方案,在通常情況下是沒有問題的,也是一種官方建議的最佳實踐。

          但是如果你的頁面復(fù)雜,你會發(fā)現(xiàn)還是有力不從心的地方。比如:

          • 組件層級太深,需要共享狀態(tài),此時狀態(tài)要層層傳遞。
          • 子組件更新一個狀態(tài),可能有多個父組件,兄弟組件共用,實現(xiàn)困難。

          這種情況下繼續(xù)使用 “提取狀態(tài)到父組件” 的方法你會發(fā)現(xiàn)很復(fù)雜。而且隨著組件增多,嵌套層級加深,這個復(fù)雜度也越來越高。因為關(guān)聯(lián)的狀態(tài)多,傳遞復(fù)雜,很容易出現(xiàn)像某個組件莫名其妙的更新,某個組件死活不更新這樣的問題,異常排查也會困難重重。

          鑒于此,我們需要一個更優(yōu)雅到方案,專門去處理這種復(fù)雜狀況下的狀態(tài)。

          需要狀態(tài)管理嗎?

          上一節(jié)我們說到,隨著頁面的復(fù)雜,我們在跨組件共享狀態(tài)的實現(xiàn)上遇到了棘手的問題。

          那么有沒有解決方案呢?當然有的,得益于社區(qū)大佬們的努力,方案還不止一個。但是這些方案都有一個共同的名字,就是我們在兩年前討論非常激烈的 ——— 狀態(tài)管理

          狀態(tài)管理,其實可以理解為全局狀態(tài)管理,這里的狀態(tài)不同于組件內(nèi)部的狀態(tài),它是獨立于組件單獨維護的,然后再通過某種方式與需要該狀態(tài)的組件關(guān)聯(lián)起來。

          狀態(tài)管理各有各的實現(xiàn)方案。Vue 有 Vuex,React 有 Redux,Mobx,當然還有其他方案。但是它們解決的都是一個問題,就是跨組件狀態(tài)共享的問題。

          我記得前兩年因為 “狀態(tài)管理” 這個概念的火熱,好像成了應(yīng)用開發(fā)不可或缺的一部分。以 Vue 為例,創(chuàng)建一個項目必然會引入 Vuex 做狀態(tài)管理。但是很多人不知道為什么用,什么時候用,怎么用狀態(tài)管理,只是盲目跟風,于是后來出現(xiàn)了非常多濫用狀態(tài)管理的例子。

          看到這里,你應(yīng)該知道狀態(tài)管理不是必須的。它為什么出現(xiàn),以及它要解決什么問題,上面基本都說明白了。如果你還沒明白,請暫停,從開頭再讀一遍。不要覺得一個技術(shù)方案誕生的背景不重要,如果你不明白它的出現(xiàn)是為了解決什么問題,那么你就無法真正發(fā)揮它的作用。

          Redux 作者有一句名言:如果你不知道是否需要 Redux(狀態(tài)管理),那就是不需要它。

          好了,如果你在用狀態(tài)管理,或需要使用狀態(tài)管理幫你解決問題,那我們繼續(xù)往下看。

          Vuex

          Vue 在國內(nèi)的應(yīng)用非常廣泛,尤其是中小團隊,因此大多人接觸到的第一個狀態(tài)管理方案應(yīng)該就是 Vuex。

          那么 Vuex 是如何解決跨組件狀態(tài)共享的問題的呢?我們一起來探索一下。

          創(chuàng)建 store

          我們上面說到,對于一般的組件共享狀態(tài),官方建議“提取狀態(tài)到最近的父組件”。Vuex 則是更高一步,將所有狀態(tài)提取到了根組件,這樣任何組件都能訪問到。

          也許你會問:這樣做不是把狀態(tài)暴露到全局了嗎?不就徹底消除模塊化的優(yōu)勢了嗎?

          其實不然。Vuex 這么做的主要目的是為了讓所有組件都可以訪問到這些狀態(tài),徹底避免子組件狀態(tài)訪問不了的情況。Vuex 把所有狀態(tài)數(shù)據(jù)都放在一個對象上,遵循單一數(shù)據(jù)源的原則。但是這并不代表狀態(tài)是堆砌的,Vuex 在這顆單一狀態(tài)樹上實現(xiàn)了自己的模塊化方案。

          別急,我們一步步來,先看看如何使用 Vuex。

          Vuex 是作為 Vue 的插件存在的,首先 npm 安裝:

          $?npm?install?--save?vuex
          復(fù)制代碼

          安裝之后,我們新建 src/store 文件夾,在這里放所有 Vuex 相關(guān)的代碼。

          新建 index.js 并寫入如下代碼。這段代碼主要的作用就是用 Vue.use 方法加載 Vuex 這個插件,然后將配置好的 Vuex.Store 實例導(dǎo)出。

          import?Vue?from?'vue'
          import?Vuex?from?'vuex'
          //?安裝插件
          Vue.use(Vuex)

          export?default?new?Vuex.Store({
          ??state:?{},
          ??mutations:?{},
          ??actions:?{},
          ??modules:?{}
          })
          復(fù)制代碼

          上面導(dǎo)出的實例我們通常稱之為 store。一個 store 中包含了存儲的狀態(tài)(state)和修改狀態(tài)的函數(shù)(mutation)等,所有狀態(tài)和相關(guān)操作都在這里定義。

          最后一步,在入口文件將上面導(dǎo)出的 store 實例掛載到 Vue 上:

          import?store?from?'./store'

          new?Vue({
          ??el:?'#app',
          ??store:?store
          })
          復(fù)制代碼

          注意:掛載這一步不是必須的。掛載這一步的作用只是為了方便在 .vue 組件中通過 this.$store 訪問我們導(dǎo)出的 store 實例。如果不掛載,直接導(dǎo)入使用也是一樣的。

          單一數(shù)據(jù)源(state)

          上一步我們用構(gòu)造函數(shù) Vuex.Store 創(chuàng)建了 store 實例,大家至少知道該怎么用 Vuex 了。這一步我們來看看 Vuex.Store 構(gòu)造函數(shù)的具體配置。

          首先是 state 配置,他的值是一個對象,用來存儲狀態(tài)。Vuex 使用 單一狀態(tài)樹 原則,將所有的狀態(tài)都放在這個對象上,便于后續(xù)的狀態(tài)定位和調(diào)試。

          比如說我們有一個初始狀態(tài) app_version 表示版本,如下:

          new?Vuex.Store({
          ??state:?{
          ????app_version:?'0.1.1'
          ??}
          }
          復(fù)制代碼

          現(xiàn)在要在組件中獲取,可以這樣:

          this.$store.state.app_version
          復(fù)制代碼

          但這并不是唯一的獲取方式,也可以這樣:

          import?store?from?'@/store'?//?@?表示?src?目錄
          store.state.app_version
          復(fù)制代碼

          為什么要強調(diào)這一點呢?因為很多小伙伴以為 Vuex 只能通過 this.$store 操作。到了非組件內(nèi),比如在請求函數(shù)中要設(shè)置某一個 Vuex 的狀態(tài),就不知道該怎么辦了。

          事實上組件中獲取狀態(tài)還有更優(yōu)雅的方法,比如 mapState 函數(shù),它讓獲取多狀態(tài)變得更簡單。

          import?{?mapState?}?from?'vuex'

          export?default?{
          ??computed:?{
          ????...?//?其他計算屬性
          ????...mapState({
          ??????version:?state?=>?state.app_version
          ????})
          ??}
          }
          復(fù)制代碼

          狀態(tài)更新方式(mutation)

          Vuex 中的狀態(tài)與組件中的狀態(tài)不同,不能直接用 state.app_version='xx' 這種方式修改。Vuex 規(guī)定修改狀態(tài)的唯一方法是提交 mutation。

          Mutation 是一個函數(shù),第一個參數(shù)為 state,它的作用就是更改 state 的狀態(tài)。

          下面定義一個名叫 increment 的 mutation,在函數(shù)內(nèi)更新 count 這個狀態(tài):

          new?Vuex.Store({
          ??state:?{
          ????count:?1
          ??},
          ??mutations:?{
          ????increment(state,?count)?{
          ??????//?變更狀態(tài)
          ??????state.count?+=?count
          ????}
          ??}
          })
          復(fù)制代碼

          然后在 .vue 組件中觸發(fā) increment

          this.$store.commit('increment',?2)
          復(fù)制代碼

          這樣綁定了 count 的視圖就會自動更新。

          同步更新

          雖然 mutation 是更新狀態(tài)的唯一方式,但實際上它還有一個限制:必須是同步更新

          為什么必須是同步更新?因為在開發(fā)過程中,我們常常會追蹤狀態(tài)的變化。常用的手段就是在瀏覽器控制臺中調(diào)試。而在 mutation 中使用異步更新狀態(tài),雖然也會使狀態(tài)正常更新,但是會導(dǎo)致開發(fā)者工具有時無法追蹤到狀態(tài)的變化,調(diào)試起來就會很困難。

          再有 Vuex 給 mutation 的定位就是更改狀態(tài),只是更改狀態(tài),別的不要參與。所謂專人干專事兒,這樣也幫助我們避免把更改狀態(tài)和自己的業(yè)務(wù)邏輯混起來,同時也規(guī)范了函數(shù)功能。

          那如果確實需要異步更新,該怎么辦呢?

          異步更新

          異步更新狀態(tài)是一個非常常見的場景,比如接口請求回來的數(shù)據(jù)要存儲,那就是異步更新。

          Vuex 提供了 action 用于異步更新狀態(tài)。與 mutation 不同的是,action 不直接更新狀態(tài),而是通過觸發(fā) mutation 間接更新狀態(tài)。因此即便使用 action 也不違背 “修改狀態(tài)的唯一方法是提交 mutation” 的原則。

          Action 允許在實際更新狀態(tài)前做一些副作用的操作,比如上面說的異步,還有數(shù)據(jù)處理,按條件提交不同的 mutation 等等??匆粋€例子:

          new?Vuex.Store({
          ??state:?{
          ????count:?1
          ??},
          ??mutations:?{
          ????add(state)?{
          ??????state.count++
          ????},
          ????reduce(state)?{
          ??????state.count--
          ????}
          ??},
          ??actions:?{
          ????increment(context,?data)?{
          ??????axios.get('**').then(res?=>?{
          ????????if?(data.iscan)?{
          ??????????context.commit('add')
          ????????}?else?{
          ??????????context.commit('reduce')
          ????????}
          ??????})
          ????}
          ??}
          })
          復(fù)制代碼

          在組件中觸發(fā) action:

          this.$store.dispatch('increment',?{?iscan:?true?})
          復(fù)制代碼

          這些就是 action 的使用方法。其實 action 最主要的作用就是請求接口,拿到需要的數(shù)據(jù),然后觸發(fā) mutation 修改狀態(tài)。

          其實這一步在組件中也可以實現(xiàn)。我看過一些方案,常見的是在組件內(nèi)寫一個請求方法,當請求成功,直接通過 this.$store.commit 方法觸發(fā) mutation 來更新狀態(tài),完全用不到 action。

          難道 action 可有可無嗎?

          也不是,在特定場景下確實需要 action 的,這個會在下一篇說。

          狀態(tài)模塊化(module)

          前面講過,Vuex 是單一狀態(tài)樹,所有狀態(tài)存放在一個對象上。同時 Vuex 有自己的模塊化方案 ,可以避免狀態(tài)堆砌到一起,變的臃腫。

          Vuex 允許我們將 store 分割成模塊(module),每個模塊擁有自己的 state、mutation、action。雖然狀態(tài)注冊在根組件,但是支持模塊分割,相當于做到了與頁面組件平級的“狀態(tài)組件”。

          為了區(qū)分,我們將被分割的模塊稱為子模塊,暴露在全局的稱為全局模塊

          我們來看基礎(chǔ)用法:

          new?Vuex.Store({
          ??modules:?{
          ????user:?{
          ??????state:?{
          ????????uname:?'ruims'
          ??????},
          ??????mutation:?{
          ????????setName(state,?name)?{
          ??????????state.name?=?name
          ????????}
          ??????}
          ????}
          ??}
          })
          復(fù)制代碼

          上面定義了 user 模塊,包含了一個 state 和一個 mutation。在組件中使用方法如下:

          //?訪問狀態(tài)
          this.$store.state.user.uname
          //?更新狀態(tài)
          this.$store.commit('setName')
          復(fù)制代碼

          大家發(fā)現(xiàn)了,訪問子模塊的 state 要通過 this.$store.state.[模塊名稱] 這種方式去訪問,觸發(fā) mutation 則與全局模塊一樣,沒有區(qū)別。

          action 與 mutation 原理一致,不細說。

          命名空間

          上面說到,子模塊觸發(fā) mutation 和 action 與全局模塊一致,那么假設(shè)全局模塊和子模塊中都有一個名為 setName 的 mutation。在組件中觸發(fā),哪個 mutation 會執(zhí)行呢?

          經(jīng)過試驗,都會執(zhí)行。官方的說法是:為了多個模塊能夠?qū)ν?mutation 或 action 作出響應(yīng)。

          其實官方做的這個兼容,我一直沒遇到實際的應(yīng)用場景,反而因為同名 mutation 導(dǎo)致誤觸發(fā)帶來了不少的麻煩??赡芄俜揭惨庾R到了這個問題,索引后來也為 mutation 和 action 做了模塊處理方案。

          這個方案,就是命名空間。

          命名空間也很簡單,在子模塊中加一個 namespaced: true 的配置即可開啟,如:

          new?Vuex.Store({
          ??modules:?{
          ????user:?{
          ??????namespaced:?true,
          ??????state:?{}
          ????}
          ??}
          })
          復(fù)制代碼

          開啟命名空間后,觸發(fā) mutation 就變成了:

          this.$store.commit('user/setName')
          復(fù)制代碼

          可見提交參數(shù)由 '[mutation]' 變成了 '[模塊名稱]/[mutation]'。

          模塊化的槽點

          上面我們介紹了 Vuex 的模塊化方案,將單一狀態(tài)樹 store 分割成多個 module,各自負責本模塊狀態(tài)的存儲和更新。

          模塊化是必要的,但是這個模塊的方案,用起來總覺得有點別扭。

          比如,總體的設(shè)計是將 store 先分模塊,模塊下在包含 state,mutation,action。

          那么按照正常理解,訪問 user 模塊下 state 應(yīng)該是這樣的:

          this.$store.user.state.uname
          復(fù)制代碼

          但是實際 API 卻是這樣的:

          this.$store.state.user.uname
          復(fù)制代碼

          這個 API 仿佛是在 state 中又各自分了模塊。我沒看過源碼,但從使用體驗上來說,這是別扭一。

          除 state 外,mutation,action 默認注冊在全局的設(shè)計,也很別扭。

          首先,官方說的多個模塊對同一 mutation 或 action 作出響應(yīng),這個功能暫無找到應(yīng)用場景。并且未配 namespace 時還要保證命名唯一,否則會導(dǎo)致誤觸發(fā)。

          其次,用 namespace 后,觸發(fā) mutation 是這樣的:

          this.$store.commit('user/setName')
          復(fù)制代碼

          這個明顯是將參數(shù)單獨處理了,為什么不是這樣:

          this.$store.user.commit('setName')
          復(fù)制代碼

          總體感受就是 Vuex 模塊化做的還不夠徹底。

          為什么吐槽

          上面說的槽點,并不是為了吐槽而吐槽。主要是感覺還有優(yōu)化空間。

          比如 this.$store.commit 函數(shù)可以觸發(fā)任何 mutation 來更改狀態(tài)。如果一個組件復(fù)雜,需要操作多個子模塊的狀態(tài),那么就很難快速的找出當前組件操作了哪些子模塊,當然也不好做權(quán)限規(guī)定。

          我希望的是,比如在 A 組件要用到 b, c 兩個子模塊的狀態(tài),不允許操作其他子模塊,那么就可以先將要用到模塊導(dǎo)入,比如這樣寫:

          import?{?a,?b?}?from?this.$store
          export?default?{
          ??methods:?{
          ????test()?{
          ??????alert(a.state.uname)?//?訪問狀態(tài)
          ??????a.commit('setName')//?修改狀態(tài)
          ????}
          ??}
          }
          復(fù)制代碼

          這樣按照模塊導(dǎo)入,查詢和使用都比較清晰。

          下一步

          前面我們詳細介紹了狀態(tài)管理的背景以及 Vuex 的使用,分享了關(guān)于官方 API 的思考。相信看到這里,你已經(jīng)對狀態(tài)管理和 Vuex 有了更深刻的認識和理解。

          然而本篇我們只介紹了 Vuex 這一個方案,狀態(tài)管理的其他方案,以及上面我們的吐槽點,能不能找到更優(yōu)的實現(xiàn)方法,這些都等著我們?nèi)L試。

          下一篇文章我們繼續(xù)深挖狀態(tài)管理,對比 Vuex 和 React,F(xiàn)luter 在狀態(tài)管理實現(xiàn)上的差異,然后在 Vue 上集成 Mobx,打造我們優(yōu)雅的應(yīng)用。

          往期精彩

          專欄[3]會長期輸出前端工程與架構(gòu)方向的文章,已發(fā)布如下:

          • 前端架構(gòu)師的 git 功力,你有幾成火候?[4]
          • 前端架構(gòu)師神技,三招統(tǒng)一代碼風格[5]

          如果喜歡我的文章,請點贊支持我吧!也歡迎關(guān)注我的專欄。

          聲明: 本文原創(chuàng),如有轉(zhuǎn)載需求,請加微信 ruidoc 聯(lián)系授權(quán)。


          關(guān)于本文

          作者:楊成功

          https://juejin.cn/post/7033682310667239460


          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 61
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97人妻视频 | 影音先锋少妇 | 日韩婬乱片A片AAA真人视频 | 中国一区二区精品 | 视频二区在线播放 |