Vuex 是什麼?html
Vuex 是一個專爲 Vue.js應用程序開發的狀態管理模式。因爲SPA應用的模塊化,每一個組件都有它各自的數據(state)、視圖(view)和方法(actions),當項目內容愈來愈多時,每一個組件中的狀態就變得很難管理。Vuex 就是採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。vue
一、單個組件中的狀態git
看一下官網提供的計數示例:github
<template> <div> <button class="btn btn-success" @click="increment">increment</button> view: {{count}} </div> </template> <script> export default { // state data () { return { count: 0 } }, // actions methods: { increment () { this.count++ } } } </script>
運行結果:vuex
從效果圖中能夠直觀的看到,每點擊一次按鈕觸發添加事件(actions),數據count(state)就會發生改變,而後映射到視圖界面(view)中。npm
下圖能夠表示 「 單項數據流 」 理念的極簡示意:json
這個狀態管理應用包含如下幾個部分:api
• state:驅動應用的數據源緩存
• view:以聲明方式將 state 映射到視圖babel
• actions:響應在 view 上的用戶輸入致使的狀態變化
二、多個組件中的狀態
當咱們的應用遇到 多個組件共享狀態 時,單向數據流的簡潔性很容易被破壞:
• 多個視圖依賴於同一狀態
• 來自不一樣視圖的行爲須要變動同一狀態
一樣是計數器,咱們如今更換一種場景,兩個相同的組件A和B,共享一個數據count,而且都有一個方法能夠操做這個count(是否是跟上面提到的多組件共享狀態描述的同樣呢)
// 組件A <template> <div> {{ $store.state.count }} <button @click="increment">組件A</button> </div> </template> <script> export default { methods: { increment () { this.$store.commit('increment') } } } </script> //組件B <template> <div> {{ $store.state.count }} <button @click="increment">組件B</button> </div> </template> <script> export default { methods: { increment () { this.$store.commit('increment') } } } </script>
運行效果:
從圖中能夠看到,「組件A」 和 「組件B」 兩個按鈕 會同時改變兩個 count 的數據,由於數據源 count 和 方法increment 都是全局的。以下圖所示,咱們把 全局數據源 state,改變數據源的方法 mutations 和 異步操做方法 actions 提取出來放到 store 中,實現全局數據狀態單獨管理的功能
安裝
一、使用 npm 安裝並保存到 package.json 中
npm install vuex --save
package.json
"dependencies": { ..., ..., ..., "vuex": "^2.4.1" },
二、配置
// 若是在模塊化構建系統中,請確保在開頭調用了 Vue.use(Vuex) import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) //建立Store實例 const store = new Vuex.Store({ // 存儲狀態值 state: { ... }, // 狀態值的改變方法,操做狀態值 // 提交mutations是更改Vuex狀態的惟一方法 mutations: { ... }, // 在store中定義getters(能夠認爲是store的計算屬性)。Getters接收state做爲其第一個函數 getters: { ... }, actions: { ... } }) // 要改變狀態值只能經過提交mutations來完成 /* eslint-disable no-new */ const app = new Vue({ router, i18n, // 將 store 實例注入到根組件下的全部子組件中,子組件經過 this.$store 來訪問store
store, ...App }) app.$mount('#app')
看一下官網提供的例子:
<template> <div> <p>{{ count }}</p> <p> <button @click="increment">+</button> <button @click="decrement">-</button> </p> </div> </template> <script> export default { computed: { count () { // 經過 store.state 來獲取狀態對象 return this.$store.state.count } }, methods: { increment () { // 經過 store.commit 方法觸發狀態變動 this.$store.commit('increment') }, decrement () { this.$store.commit('decrement') } } } </script>
// 建立 Store 實例 const store = new Vuex.Store({ // 存儲狀態值 state: { count: 0 }, // 狀態值的改變方法,操做狀態值 // 提交 mutations 是更改Vuex狀態的惟一方法 mutations: { increment: state => state.count++, decrement: state => state.count-- } })
運行效果:
核心概念
一、State
state 就是全局的狀態(數據源),從前面的例子中看到咱們能夠按以下方式獲取 Vuex 的state 狀態
// html 中 {{ $store.state.count }} // js 中 this.$store.state.count
二、Getter
getter 能夠認爲是 store 的計算屬性,跟計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會從新計算
以下官網提供的案例:
computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }
若是有多個組件須要用到此屬性,咱們要麼複製這個函數,或者抽取到一個共享函數而後在多處導入它,然而這兩種方法都不是很理想,最佳方式固然是使用 getter 了
咱們嘗試使用下getter
(1)、定義 getter
const store = new Vuex.Store({ state: { count: 0 }, getters: { formatMoney: state => { return '¥'+state.count.toFixed(2)+'元' } }, mutations: { increment: state => state.count++ } })
(2)、在組件中引用 getter
export default { methods: { increment () { this.$store.commit('increment') // 這裏爲了更清楚的看到計算後的值 let aaa = document.getElementById('aaa') let p = document.createElement('p') p.innerHTML = this.$store.getters.formatMoney aaa.appendChild(p) } }, computed: { formatMoney() { return this.$store.getters.formatMoney } } }
效果:
三、Mutation
更改 Vuex 的 store 中的狀態的惟一方法就是提交 mutation。Vuex 中的 mutation 很是相似於事件:每一個 mutation 都有一個字符串的 事件類型(type)和一個 回調函數(handler),這個回調函數就是咱們實際進行狀態更改的地方,而且它會接受 state 做爲第一個參數:
const store = new Vuex.Store({ state: { count: 1 }, mutations: { increment (state) { // 變動狀態 state.count++ } } })
要喚醒一個 mutation handler,你須要以相應的 type 調用 store.commit 方法
store.commit('increment')
(1)、提交載荷(Payload)
載荷(payload)就是說 能夠向 store.commit 傳入額外的參數
// ... mutations: { increment (state, n) { state.count += n } } store.commit('increment', 10)
在大多數狀況下,載荷應該是一個對象,這樣能夠包含多個字段而且記錄的mutation會更易讀:
// ... mutations: { increment (state, payload) { state.count += payload.amount } } store.commit('increment', { amount: 10 })
四、Action
Vuex 中一條重要的原則就是 mutation 必須是同步函數, action 相似於 mutation,不一樣之處在於:
• Action 提交的是 mutation,而不是直接變動狀態
• Action 能夠包含任意異步操做
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') }, // 異步 incrementAsync (context) { // 延時1秒 setTimeout(() => { context.commit('increment') }, 1000) } } })
Action 函數接受一個與 store 實例具備相同方法和屬性的context對象,所以,能夠有如下調用方法
• context.commit 提交一個 mutation
• context.state 獲取 state
• context.getters 獲取 getters
不一樣於 mutation 使用 commit 方法,action 使用 dispatch 方法
store.dispatch('increment')
Actions 一樣支持 載荷方式 和 對象方式 進行分發:
// 以載荷形式分發 store.dispatch('incrementAsync', { amount: 10 }) // 以對象形式分發 store.dispatch({ type: 'incrementAsync', amount: 10 })
五、Module
因爲使用單一狀態樹,應用的全部狀態會集中到一個比較大的對象,當應用變得很是複雜時,store 對象就有可能變得很是臃腫。
爲了解決以上問題,Vuex 容許咱們將 store 分割成 模塊(module),每一個模塊擁有本身的 state、mutation、getter、action,甚至是嵌套子模塊 --- 從上至下進行一樣方式的分割
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 的狀態
關於項目結構,咱們能夠看看官網提供的示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API請求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 咱們組裝模塊並導出 store 的地方
├── actions.js # 根級別的 action
├── mutations.js # 根級別的 mutation
└── modules
├── cart.js # 購物車模塊
└── products.js # 產品模塊
官網同時也提供了一個 購物車 示例:
app.js 文件以下:
import 'babel-polyfill' import Vue from 'vue' import App from './components/App.vue' import store from './store' import { currency } from './currency' Vue.filter('currency', currency) new Vue({ el: '#app', store, render: h => h(App) })
index.js 文件以下:
import Vue from 'vue' import Vuex from 'vuex' import * as actions from './actions' import * as getters from './getters' import cart from './modules/cart' import products from './modules/products' import createLogger from '../../../src/plugins/logger' Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ actions, getters, modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : [] })
getters.js 文件以下:
export const cartProducts = state => { return state.cart.added.map(({ id, quantity }) => { const product = state.products.all.find(p => p.id === id) return { title: product.title, price: product.price, quantity } }) }
actions.js 文件以下:
import * as types from './mutation-types' export const addToCart = ({ commit }, product) => { if (product.inventory > 0) { commit(types.ADD_TO_CART, { id: product.id }) } }
mutation-type.js 文件以下:
export const ADD_TO_CART = 'ADD_TO_CART' export const CHECKOUT_REQUEST = 'CHECKOUT_REQUEST' export const CHECKOUT_SUCCESS = 'CHECKOUT_SUCCESS' export const CHECKOUT_FAILURE = 'CHECKOUT_FAILURE' export const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS'
cart.js 文件以下:
import shop from '../../api/shop' import * as types from '../mutation-types' // initial state // shape: [{ id, quantity }] const state = { added: [], checkoutStatus: null } // getters const getters = { checkoutStatus: state => state.checkoutStatus } // actions const actions = { checkout ({ commit, state }, products) { const savedCartItems = [...state.added] commit(types.CHECKOUT_REQUEST) shop.buyProducts( products, () => commit(types.CHECKOUT_SUCCESS), () => commit(types.CHECKOUT_FAILURE, { savedCartItems }) ) } } // mutations const mutations = { [types.ADD_TO_CART] (state, { id }) { state.lastCheckout = null const record = state.added.find(p => p.id === id) if (!record) { state.added.push({ id, quantity: 1 }) } else { record.quantity++ } }, [types.CHECKOUT_REQUEST] (state) { // clear cart state.added = [] state.checkoutStatus = null }, [types.CHECKOUT_SUCCESS] (state) { state.checkoutStatus = 'successful' }, [types.CHECKOUT_FAILURE] (state, { savedCartItems }) { // rollback to the cart saved before sending the request state.added = savedCartItems state.checkoutStatus = 'failed' } } export default { state, getters, actions, mutations }
products.js 文件以下:
import shop from '../../api/shop' import * as types from '../mutation-types' // initial state const state = { all: [] } // getters const getters = { allProducts: state => state.all } // actions const actions = { getAllProducts ({ commit }) { shop.getProducts(products => { commit(types.RECEIVE_PRODUCTS, { products }) }) } } // mutations const mutations = { [types.RECEIVE_PRODUCTS] (state, { products }) { state.all = products }, [types.ADD_TO_CART] (state, { id }) { state.all.find(p => p.id === id).inventory-- } } export default { state, getters, actions, mutations }
購物車運行效果: