推薦:《5個vue.js影片教學精選》
#以Vuex 為引,一窺狀態管理全貌
眾所周知,Vuex 是Vue 官方的狀態管理方案。
Vuex 的用法和 API 不難,官網介紹也簡潔明了。得益於此,將 Vuex 快速整合到專案中非常容易。然而正因為用法靈活,許多同學在 Vuex 的設計和使用上反而有些混亂。
其實在使用前,我們不妨暫停一下,思考幾個問題:
- 什麼是狀態管理?
- 我為什麼要用 Vuex?
- 元件內部狀態和 Vuex 狀態如何分配?
- 使用 Vuex 會有哪些潛在問題?
如果你對這些問題模稜兩可,那麼恭喜你,這篇文章可能是你需要的。
下面請和我一起,從起源開始,以 Vuex 為例,共同揭開狀態管理的神秘面紗。
大綱預覽
本文介紹的內容包含以下面向:
- 狀態與元件的誕生
- 需要狀態管理嗎?
- 單一資料來源
- 狀態更新方式
- #非同步更新?
- 狀態模組化
- 模組化的槽點
- 下一步
#狀態與元件的誕生
自三大框架誕生起,它們共有的兩個能力徹底暴擊了Jquery。這兩個能力分別是:
- 資料驅動視圖
- #元件化
資料驅動視圖,使我們告別了只能依靠操作DOM 更新頁面的時代。我們不再需要每次更新頁面時,透過層層 find 找到 DOM 然後修改它的屬性和內容,可以透過操作資料來實現這些事情。
當然了在我們前端的眼裡,資料基本上可以理解為儲存各種資料類型的 變數
。在 資料驅動
這個概念出現之後,一部分變數也被賦予了特殊的意義。
首先是普通變量,和 JQ 時代沒差,只用來儲存資料。除此之外還有一類變量,它們有響應式的作用,這些變量與視圖綁定,當變量改變時,綁定了這些變量的視圖也會觸發對應的更新,這類變量我稱之為狀態變數。
所謂資料驅動視圖,嚴格說就是狀態變數在驅動視圖。隨著 Vue,React 的大力普及之下,前端開發們的工作重心逐漸從操作 DOM 轉移到了操作數據,狀態變數成為了核心。
狀態變量,現在大家似乎更願意稱之為狀態。我們常常詞不離口的狀態,狀態管理,其實這個狀態就是指狀態變數。下文提到的狀態同樣也是指狀態變數。
有了狀態之後,元件也來了。
JQ 時代的前端一個頁面就是一個 html,沒有「元件」的概念,對於頁面中的公共部分,想要優雅的實現復用簡直不要太難。所幸三大框架帶來了非常成熟的組件設計,可以輕鬆的抽取一個 DOM 片段作為組件,而且組件內部可以維護自己的狀態,獨立性更高。
元件的一個重要特性,就是內部的這些狀態是對外隔離的。父元件無法存取到子元件內部的狀態,但是子元件可以存取父元件顯示傳過來的狀態(Props),並且根據變更自動回應。
這個特性可以理解為狀態被模組化了。這樣的好處是,不需要考慮目前設定的狀態會影響到其他元件。當然了組件狀態徹底隔離也是不切實際的,必然會有多個組件共享狀態的需求,這種情況的方案就是將狀態提取到離這些組件最近的父組件,透過 Props 向下傳遞。
上述共享狀態的方案,在通常情況下是沒有問題的,也是一種官方建議的最佳實踐。
但是如果你的頁面複雜,你會發現還是有力不從心的地方。例如:
- 元件層級太深,需要共享狀態,此時狀態要層層傳遞。
- 子元件更新一個狀態,可能有多個父元件,兄弟元件共用,實現困難。
這種情況下繼續使用 “提取狀態到父元件” 的方法你會發現很複雜。而且隨著組件增多,嵌套層級加深,這個複雜度也越來越高。因為關聯的狀態多,傳遞複雜,很容易出現像某個組件莫名其妙的更新,某個組件死活不更新這樣的問題,異常排查也會困難重重。
有鑑於此,我們需要一個更優雅到方案,專門去處理這種複雜狀況下的狀態。
需要狀態管理嗎?
上一節我們說到,隨著頁面的複雜,我們在跨元件共享狀態的實作上遇到了棘手的問題。
那麼有沒有解決方案呢?當然有的,得益於社區大佬們的努力,方案不只一個。但這些方案都有一個共同的名字,就是我們在兩年前討論非常激烈的 ——— 狀態管理。
狀態管理,其實可以理解為全域狀態管理,這裡的狀態不同於元件內部的狀態,它是獨立於元件單獨維護的,然後再透過某種方式與需要該狀態的組件關聯起來。
狀態管理各有各的實現方案。 Vue 有 Vuex,React 有 Redux,Mobx,當然還有其他方案。但是它們解決的都是一個問題,就是跨元件狀態共享的問題。
我記得前兩年因為 「狀態管理」 這個概念的火熱,好像成了應用開發不可或缺的一部分。以 Vue 為例,建立一個專案必然會引入 Vuex 做狀態管理。但很多人不知道為什麼用,什麼時候用,怎麼用狀態管理,只是盲目跟風,於是後來出現了非常多濫用狀態管理的例子。
看到這裡,你應該知道狀態管理不是必須的。它為什麼會出現,以及它要解決什麼問題,上面基本上都說明白了。如果你還沒明白,請暫停,從開頭再讀一次。不要覺得一個技術方案誕生的背景不重要,如果你不明白它的出現是為了解決什麼問題,那麼你就無法真正發揮它的作用。
Redux 作者有一句名言:如果你不知道是否需要 Redux(狀態管理),那就是不需要它。
好了,如果你在用狀態管理,或需要使用狀態管理幫你解決問題,那我們繼續往下看。
Vuex
Vue 在國內的應用非常廣泛,尤其是中小團隊,因此大多人接觸到的第一個狀態管理方案應該是 Vuex。
那麼 Vuex 是如何解決跨元件狀態共享的問題的呢?我們一起來探索一下。
建立 store
我們上面說到,對於一般的元件共享狀態,官方建議「提取狀態到最近的父元件」。 Vuex 則是更高一步,將所有狀態提取到了根元件,這樣任何元件都能存取。
也許你會問:這樣做不是把狀態暴露到全域了嗎?不就徹底消除模組化的優勢了嗎?
其實不然。 Vuex 這麼做的主要目的是為了讓所有元件都可以存取這些狀態,徹底避免子元件狀態存取不了的情況。 Vuex 把所有狀態資料放在一個物件上,遵循單一資料來源的原則。但這並不代表狀態是堆砌的,Vuex 在這顆單一狀態樹上實現了自己的模組化方案。
別急,我們一步一步來,先看看如何使用 Vuex。
Vuex 是作為Vue 的插件存在的,首先npm 安裝:
$ npm install --save vuex
安裝之後,我們新建src/store
資料夾,在這裡放所有Vuex 相關的代碼。
新 index.js
並寫入以下程式碼。這段程式碼主要的作用就是用 Vue.use
方法載入 Vuex 這個插件,然後將配置好的 Vuex.Store
實例匯出。
import Vue from 'vue' import Vuex from 'vuex' // 安装插件 Vue.use(Vuex) export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules: {} })
上面匯出的實例我們通常稱之為 store
。一個 store 包含了儲存的狀態(state
)和修改狀態的函數(mutation
)等,所有狀態和相關操作都在這裡定義。
最後一步,在入口檔案將上面匯出的 store 實例掛載到 Vue 上:
import store from './store' new Vue({ el: '#app', store: store })
注意:掛載這一步不是必須的。掛載這一步驟的功能只是為了方便在 .vue 元件中透過 this.$store
存取我們匯出的 store 實例。如果不掛載,直接導入使用也是一樣的。
單一資料來源(state)
上一步我們用建構子 Vuex.Store
建立了 store 實例,大家至少知道該怎麼用 Vuex 了。這一步我們來看看 Vuex.Store 建構函數的具體配置。
首先是 state
配置,他的值是一個對象,用來儲存狀態。 Vuex 使用 單一狀態樹
原則,將所有的狀態都放在這個物件上,以便於後續的狀態定位和除錯。
比如說我們有一個初始狀態app_version
表示版本,如下:
new Vuex.Store({ state: { app_version: '0.1.1' } }
現在要在元件中取得,可以這樣:
this.$store.state.app_version
但這不是唯一的取得方式,也可以這樣:
import store from '@/store' // @ 表示 src 目录 store.state.app_version
為什麼要強調這一點呢?因為很多小夥伴以為 Vuex 只能透過 this.$store
操作。到了非元件內,例如在請求函數中要設定某一個 Vuex 的狀態,就不知道該怎麼辦了。
事實上元件中取得狀態還有更優雅的方法,例如 mapState
函數,它讓取得多狀態變得更簡單。
import { mapState } from 'vuex' export default { computed: { ... // 其他计算属性 ...mapState({ version: state => state.app_version }) } }
状态更新方式(mutation)
Vuex 中的状态与组件中的状态不同,不能直接用 state.app_version='xx'
这种方式修改。Vuex 规定修改状态的唯一方法是提交 mutation
。
Mutation 是一个函数,第一个参数为 state,它的作用就是更改 state 的状态。
下面定义一个名叫 increment 的 mutation,在函数内更新 count 这个状态:
new Vuex.Store({ state: { count: 1 }, mutations: { increment(state, count) { // 变更状态 state.count += count } } })
然后在 .vue 组件中触发 increment:
this.$store.commit('increment', 2)
这样绑定了 count 的视图就会自动更新。
同步更新
虽然 mutation 是更新状态的唯一方式,但实际上它还有一个限制:必须是同步更新。
为什么必须是同步更新?因为在开发过程中,我们常常会追踪状态的变化。常用的手段就是在浏览器控制台中调试。而在 mutation 中使用异步更新状态,虽然也会使状态正常更新,但是会导致开发者工具有时无法追踪到状态的变化,调试起来就会很困难。
再有 Vuex 给 mutation 的定位就是更改状态,只是更改状态,别的不要参与。所谓专人干专事儿,这样也帮助我们避免把更改状态和自己的业务逻辑混起来,同时也规范了函数功能。
那如果确实需要异步更新,该怎么办呢?
异步更新
异步更新状态是一个非常常见的场景,比如接口请求回来的数据要存储,那就是异步更新。
Vuex 提供了 action
用于异步更新状态。与 mutation 不同的是,action 不直接更新状态,而是通过触发 mutation 间接更新状态。因此即便使用 action 也不违背 “修改状态的唯一方法是提交 mutation” 的原则。
Action 允许在实际更新状态前做一些副作用的操作,比如上面说的异步,还有数据处理,按条件提交不同的 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') } }) } } })
在组件中触发 action:
this.$store.dispatch('increment', { iscan: true })
这些就是 action 的使用方法。其实 action 最主要的作用就是请求接口,拿到需要的数据,然后触发 mutation 修改状态。
其实这一步在组件中也可以实现。我看过一些方案,常见的是在组件内写一个请求方法,当请求成功,直接通过 this.$store.commit
方法触发 mutation 来更新状态,完全用不到 action。
难道 action 可有可无吗?
也不是,在特定场景下确实需要 action 的,这个会在下一篇说。
状态模块化(module)
前面讲过,Vuex 是单一状态树,所有状态存放在一个对象上。同时 Vuex 有自己的模块化方案
,可以避免状态堆砌到一起,变的臃肿。
Vuex 允许我们将 store 分割成模块(module),每个模块拥有自己的 state、mutation、action。虽然状态注册在根组件,但是支持模块分割,相当于做到了与页面组件平级的“状态组件”。
为了区分,我们将被分割的模块称为子模块,暴露在全局的称为全局模块。
我们来看基础用法:
new Vuex.Store({ modules: { user: { state: { uname: 'ruims' }, mutation: { setName(state, name) { state.name = name } } } } })
上面定义了 user
模块,包含了一个 state 和一个 mutation。在组件中使用方法如下:
// 访问状态 this.$store.state.user.uname // 更新状态 this.$store.commit('setName')
大家发现了,访问子模块的 state 要通过 this.$store.state.[模块名称]
这种方式去访问,触发 mutation 则与全局模块一样,没有区别。
action 与 mutation 原理一致,不细说。
命名空间
上面说到,子模块触发 mutation 和 action 与全局模块一致,那么假设全局模块和子模块中都有一个名为 setName
的 mutation。在组件中触发,哪个 mutation 会执行呢?
经过试验,都会执行。官方的说法是:为了多个模块能够对同一 mutation 或 action 作出响应。
其实官方做的这个兼容,我一直没遇到实际的应用场景,反而因为同名 mutation 导致误触发带来了不少的麻烦。可能官方也意识到了这个问题,索引后来也为 mutation 和 action 做了模块处理方案。
这个方案,就是命名空间。
命名空间也很简单,在子模块中加一个 namespaced: true
的配置即可开启,如:
new Vuex.Store({ modules: { user: { namespaced: true, state: {} } } })
开启命名空间后,触发 mutation 就变成了:
this.$store.commit('user/setName')
可见提交参数由 '[mutation]'
变成了 '[模块名称]/[mutation]'
。
模块化的槽点
上面我们介绍了 Vuex 的模块化方案,将单一状态树 store 分割成多个 module,各自负责本模块状态的存储和更新。
模块化是必要的,但是这个模块的方案,用起来总觉得有点别扭。
比如,总体的设计是将 store 先分模块,模块下在包含 state,mutation,action。
那么按照正常理解,访问 user 模块下 state 应该是这样的:
this.$store.user.state.uname
但是实际 API 却是这样的:
this.$store.state.user.uname
这个 API 仿佛是在 state 中又各自分了模块。我没看过源码,但从使用体验上来说,这是别扭一。
除 state 外,mutation,action 默认注册在全局的设计,也很别扭。
首先,官方说的多个模块对同一 mutation 或 action 作出响应,这个功能暂无找到应用场景。并且未配 namespace 时还要保证命名唯一,否则会导致误触发。
其次,用 namespace 后,触发 mutation 是这样的:
this.$store.commit('user/setName')
这个明显是将参数单独处理了,为什么不是这样:
this.$store.user.commit('setName')
总体感受就是 Vuex 模块化做的还不够彻底。
为什么吐槽
上面说的槽点,并不是为了吐槽而吐槽。主要是感觉还有优化空间。
比如 this.$store.commit
函数可以触发任何 mutation 来更改状态。如果一个组件复杂,需要操作多个子模块的状态,那么就很难快速的找出当前组件操作了哪些子模块,当然也不好做权限规定。
我希望的是,比如在 A 组件要用到 b, c
两个子模块的状态,不允许操作其他子模块,那么就可以先将要用到模块导入,比如这样写:
import { a, b } from this.$store export default { methods: { test() { alert(a.state.uname) // 访问状态 a.commit('setName')// 修改状态 } } }
这样按照模块导入,查询和使用都比较清晰。
下一步
前面我们详细介绍了状态管理的背景以及 Vuex 的使用,分享了关于官方 API 的思考。相信看到这里,你已经对状态管理和 Vuex 有了更深刻的认识和理解。
然而本篇我们只介绍了 Vuex 这一个方案,状态管理的其他方案,以及上面我们的吐槽点,能不能找到更优的实现方法,这些都等着我们去尝试。
下一篇文章我们继续深挖状态管理,对比 Vuex 和 React,Fluter 在状态管理实现上的差异,然后在 Vue 上集成 Mobx,打造我们优雅的应用。
以上是以Vuex為例,揭開狀態管理的神秘面紗的詳細內容。更多資訊請關注PHP中文網其他相關文章!

Vue.js適合快速開發和小型項目,而React更適合大型和復雜的項目。 1.Vue.js簡單易學,適用於快速開發和小型項目。 2.React功能強大,適合大型和復雜的項目。 3.Vue.js的漸進式特性適合逐步引入功能。 4.React的組件化和虛擬DOM在處理複雜UI和數據密集型應用時表現出色。

Vue.js和React各有優缺點,選擇時需綜合考慮團隊技能、項目規模和性能需求。 1)Vue.js適合快速開發和小型項目,學習曲線低,但深層嵌套對象可能導致性能問題。 2)React適用於大型和復雜應用,生態系統豐富,但頻繁更新可能導致性能瓶頸。

Vue.js適合小型到中型項目,React適合大型項目和復雜應用場景。 1)Vue.js易於上手,適用於快速原型開發和小型應用。 2)React在處理複雜狀態管理和性能優化方面更有優勢,適合大型項目。

Vue.js和React各有優勢:Vue.js適用於小型應用和快速開發,React適合大型應用和復雜狀態管理。 1.Vue.js通過響應式系統實現自動更新,適用於小型應用。 2.React使用虛擬DOM和diff算法,適合大型和復雜應用。選擇框架時需考慮項目需求和團隊技術棧。

Vue.js和React各有優勢,選擇應基於項目需求和團隊技術棧。 1.Vue.js社區友好,提供豐富學習資源,生態系統包括VueRouter等官方工具,支持由官方團隊和社區提供。 2.React社區偏向企業應用,生態系統強大,支持由Facebook及其社區提供,更新頻繁。

Netflix使用React來提升用戶體驗。 1)React的組件化特性幫助Netflix將復雜UI拆分成可管理模塊。 2)虛擬DOM優化了UI更新,提高了性能。 3)結合Redux和GraphQL,Netflix高效管理應用狀態和數據流動。

Vue.js是前端框架,後端框架用於處理服務器端邏輯。 1)Vue.js專注於構建用戶界面,通過組件化和響應式數據綁定簡化開發。 2)後端框架如Express、Django處理HTTP請求、數據庫操作和業務邏輯,運行在服務器上。

Vue.js與前端技術棧緊密集成,提升開發效率和用戶體驗。 1)構建工具:與Webpack、Rollup集成,實現模塊化開發。 2)狀態管理:與Vuex集成,管理複雜應用狀態。 3)路由:與VueRouter集成,實現單頁面應用路由。 4)CSS預處理器:支持Sass、Less,提升樣式開發效率。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

Atom編輯器mac版下載
最受歡迎的的開源編輯器

Dreamweaver CS6
視覺化網頁開發工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。