Vuex 學習筆記

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
}

購物車運行效果:

相關文章
相關標籤/搜索