Vuex —— vue的狀態(tài)管理工具

一. 什么是Vuex?

Vuex是一個專門為Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式昵仅, 它采用集中式存儲管理所有組件的公共狀態(tài),导绷,并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。

image.png

上圖中綠色虛線包裹起來的部分就是Vuex的核心蕴纳,state中保存的就是公共狀態(tài),会油,改變state的唯一方式就是通過mutations進(jìn)行更改。

二. 為什么要使用Vuex?

試想這樣的場景, 比如一個Vue的根實(shí)例下面有一個根組件名為App.vue, 它下面有兩個子組件A.vue和B.vue, App.vue想要與A.vue或者B.vue通訊可以通過props傳值的方式, 但是如果A.vue和B.vue之間的通訊就很麻煩了, 他們需要共有的父組件通過自定義事件進(jìn)行實(shí)現(xiàn).
A組件想要和B組件通訊往往是這樣的:


image.png

A組件說: "報告老大, 能否幫我托個信給小弟B" => dispatch一個事件給App
App老大說: "包在我身上, 它需要監(jiān)聽A組件的dispatch的時間, 同時需要broadcast一個事件給B組件"
B小弟說: "信息已收到", 它需要on監(jiān)聽App組件分發(fā)的事件

這只是一條通訊路徑, 如果父組件下有多個子組件, 子組件之間通訊的路徑就會變的很繁瑣, 父組件需要監(jiān)聽大量的事件, 還需要負(fù)責(zé)分發(fā)給不同的子組件, 很顯然這并不是我們想要的組件化的開發(fā)體驗(yàn).

而Vuex就是為了解決這一問題出現(xiàn)的古毛。
三.如何引入Vuex?

核心知識:

state: 狀態(tài)中心
mutations: 更改狀態(tài)
actions: 異步更改狀態(tài)
getters: 獲取狀態(tài)
modules: 將state分成多個modules翻翩,便于管理
  1. 首先使用 vue-init 初始化一個 vue-demo;
  2. 然后在src文件目錄下新建一個名為store的文件夾稻薇,為方便引入并在store文件夾里新建一個index.js嫂冻,里面的內(nèi)容如下:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store();
 
export default store;

3.接下來,在 main.js里面引入store塞椎,然后再全局注入一下桨仿,這樣一來就可以在任何一個組件里面使用this.$store了:

import store from './store'  // 引入store
 
new Vue({
  el: '#app',
  router,
  store,                     // 使用store
  template: '<App/>',
  components: { App }
})

代碼和初始化的效果如下圖所示:


image.png
//App.vue中的初始化代碼

<template>
<div id="app">
    <product-list-one v-bind:products="products"></product-list-one>
    <product-list-two v-bind:products="products"></product-list-two>
</div>
</template>

<script>
import ProductListOne from './components/ProductListOne.vue'
import ProductListTwo from './components/ProductListTwo.vue'

export default {
    name: 'app',
    components: {
        'product-list-one': ProductListOne,
        'product-list-two': ProductListTwo
    },
    data () {
        return {
            products: [
                {name: '鼠標(biāo)', price: 200},
                {name: '鍵盤', price: 400},
                {name: '耳機(jī)', price: 600},
                {name: '顯示屏', price: 800}
            ]
        }
    }
}
</script>

<style>
body{
    font-family: Ubuntu;
    color: #555;
}
</style>
// components/ProductListOne.vue
<template>
    <div id="product-list-one">
        <h2>Product List One</h2>
        <ul>
            <li v-for="product in products">
                <span class="name">{{ product.name }}</span>
                <span class="price">${{ product.price }}</span>
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    props: ['products'],
    data () {
        return {

        }
    }
}
</script>

<style scoped>
#product-list-one{
    background: #FFF8B1;
    box-shadow: 1px 2px 3px rgba(0,0,0,0.2);
    margin-bottom: 30px;
    padding: 10px 20px;
}
#product-list-one ul{
    padding: 0;
}
#product-list-one li{
    display: inline-block;
    margin-right: 10px;
    margin-top: 10px;
    padding: 20px;
    background: rgba(255,255,255,0.7);
}
.price{
    font-weight: bold;
    color: #E8800C;
}
</style>
//components/ProductListTwo.vue
<template>
    <div id="product-list-two">
        <h2>Product List Two</h2>
        <ul>
            <li v-for="product in products">
                <span class="name">{{ product.name }}</span>
                <span class="price">${{ product.price }}</span>
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    props: ['products'],
    data () {
        return {

        }
    }
}
</script>

<style scoped>
#product-list-one{
    background: #FFF8B1;
    box-shadow: 1px 2px 3px rgba(0,0,0,0.2);
    margin-bottom: 30px;
    padding: 10px 20px;
}
#product-list-one ul{
    padding: 0;
}
#product-list-one li{
    display: inline-block;
    margin-right: 10px;
    margin-top: 10px;
    padding: 20px;
    background: rgba(255,255,255,0.7);
}
.price{
    font-weight: bold;
    color: #E8800C;
}
</style>
核心概念1: State

state就是Vuex中的公共的狀態(tài), 我是將state看作是所有組件的data, 用于保存所有組件的公共數(shù)據(jù).

此時我們就可以把App.vue中的兩個組件共同使用的data抽離出來, 放到state中,
store/index.js 代碼如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {    //要設(shè)置的全局訪問的state對象
        products: [
            {name: '鼠標(biāo)', price: 200},
            {name: '鍵盤', price: 400},
            {name: '耳機(jī)', price: 600},
            {name: '顯示屏', price: 800}
        ]
    }
})

export default store;

此時,ProductListOne.vue和ProductListTwo.vue也需要做相應(yīng)的更改

//ProductListOne.vue
export default {
    data () {
        return {
            products : this.$store.state.products //獲取store中state的數(shù)據(jù)
        }
    }
}
//ProductListTwo.vue
export default {
    data () {
        return {
            products: this.$store.state.products //獲取store中state的數(shù)據(jù)
        }
    }
}
核心概念2: Getters

將getters屬性理解為所有組件的computed屬性, 也就是計算屬性. vuex的官方文檔也是說到可以將getter理解為store的計算屬性, getters的返回值會根據(jù)它的依賴被緩存起來,且只有當(dāng)它的依賴值發(fā)生了改變才會被重新計算案狠。

此時,我們可以在index.js中添加一個getters屬性, 其中的saleProducts對象將state中的價格減少一半(除以2)

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {    //要設(shè)置的全局訪問的state對象
        products: [
            {name: '鼠標(biāo)', price: 200},
            {name: '鍵盤', price: 400},
            {name: '耳機(jī)', price: 600},
            {name: '顯示屏', price: 800}
        ]
    },
    getters:{ //添加getters
        saleProducts: (state) => {
          let saleProducts = state.products.map( product => {
            return {
              name: product.name,
              price: product.price / 2
            }
          })
          return saleProducts;
        }
    } 
})
export default store;

將productListOne.vue中的products的值更換為this.$store.getters.saleProducts

export default {
    data () {
        return {
            products: this.$store.getters.saleProducts 
        }
    }
}

現(xiàn)在的頁面中,Product List One中的每項(xiàng)商品的價格都減少了一半


image.png
核心概念3: Mutations

將mutaions理解為store中的methods, mutations對象中保存著更改數(shù)據(jù)的回調(diào)函數(shù),該函數(shù)名官方規(guī)定叫type, 第一個參數(shù)是state, 第二參數(shù)是payload, 也就是自定義的參數(shù).

下面,我們在index.js中添加mutations屬性,其中minusPrice這個回調(diào)函數(shù)用于將商品的價格減少payload這么多, 代碼如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {    //要設(shè)置的全局訪問的state對象
        products: [
            {name: '鼠標(biāo)', price: 200},
            {name: '鍵盤', price: 400},
            {name: '耳機(jī)', price: 600},
            {name: '顯示屏', price: 800}
        ]
    },
    getters:{ //添加getters
        saleProducts: (state) => {
          let saleProducts = state.products.map( product => {
            return {
              name: product.name,
              price: product.price / 2
            }
          })
          return saleProducts;
        }
    },
    mutations:{ //添加mutations
        minusPrice (state, payload ) {
          let newPrice = state.products.forEach( product => {
            product.price -= payload
          })
        }
    }
})

export default store;

在ProductListTwo.vue中添加一個按鈕,為其添加一個點(diǎn)擊事件, 給點(diǎn)擊事件觸發(fā)minusPrice方法:

<template>
    <div id="product-list-two">
        <h2>Product List Two</h2>
        <ul>
            <li v-for="product in products">
                <span class="name">{{ product.name }}</span>
                <span class="price">${{ product.price }}</span>
            </li>
            <button @click="minusPrice">減少價格</button>
        </ul>
    </div>
</template>

在ProductListTwo.vue中注冊minusPrice方法, 在該方法中commitmutations中的minusPrice這個回調(diào)函數(shù)

注意:調(diào)用mutaions中回調(diào)函數(shù), 只能使用store.commit(type, payload)
export default {
    data () {
        return {
            products: this.$store.state.products 
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2)
        },
    }
}

點(diǎn)擊按鈕, 可以發(fā)現(xiàn), Product List Two中的價格減少了2, 當(dāng)然你可以自定義payload,以此自定義減少對應(yīng)的價格.


image.png
(Product List One中的價格沒有發(fā)生變化, 是因?yàn)間etters將價格進(jìn)行了緩存)
核心概念4: Actions

actions 類似于 mutations服傍,不同在于:

actions提交的是mutations,而不是直接變更狀態(tài)
actions中可以包含異步操作, mutations中絕對不允許出現(xiàn)異步
actions中的回調(diào)函數(shù)的第一個參數(shù)是context, 是一個與store實(shí)例具有相同屬性和方法的對象.

此時,我們在store中添加actions屬性, 其中minusPriceAsync采用setTimeout來模擬異步操作,延遲2s執(zhí)行 該方法用于異步改變我們剛才在mutaions中定義的minusPrice:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);

const store = new Vuex.Store({
    state: {    //要設(shè)置的全局訪問的state對象
        products: [
            {name: '鼠標(biāo)', price: 200},
            {name: '鍵盤', price: 400},
            {name: '耳機(jī)', price: 600},
            {name: '顯示屏', price: 800}
        ]
    },
    getters:{ //添加getters
        saleProducts: (state) => {
          let saleProducts = state.products.map( product => {
            return {
              name: product.name,
              price: product.price / 2
            }
          })
          return saleProducts;
        }
    },
    mutations:{ //添加mutations
        minusPrice (state, payload ) {
          let newPrice = state.products.forEach( product => {
            product.price -= payload
          })
        }
    },
    actions:{ //添加actions
        minusPriceAsync( context, payload ) {
          setTimeout( () => {
            context.commit( 'minusPrice', payload ); //context提交
          }, 2000)
        }
    }
})

export default store;

在ProductListTwo.vue中添加一個按鈕,為其添加一個點(diǎn)擊事件, 給點(diǎn)擊事件觸發(fā)minusPriceAsync方法

<template>
    <div id="product-list-two">
        <h2>Product List Two</h2>
        <ul>
            <li v-for="product in products">
                <span class="name">{{ product.name }}</span>
                <span class="price">${{ product.price }}</span>
            </li>
            <button @click="minusPrice">減少價格</button>
            <button @click="minusPriceAsync">異步減少價格</button> //添加按鈕
        </ul>
    </div>
</template>

在ProductListTwo.vue中注冊minusPriceAsync方法, 在該方法中dispatchactions中的minusPriceAsync這個回調(diào)函數(shù)

export default {
    data () {
        return {
            products: this.$store.state.products
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2);
        },
        minusPriceAsync() {
            this.$store.dispatch('minusPriceAsync', 5); //分發(fā)actions中的minusPriceAsync這個異步函數(shù)
        }
    }
}

點(diǎn)擊按鈕, 可以發(fā)現(xiàn), Product List Two中的價格延遲2s后減少了5,


image.png
mutations 和 actions 的區(qū)別
// 只有 mutations 才能改變 state(mutations 的參數(shù)是 state)骂铁,通過state去獲取商品改變價格吹零,使用的時候通過 commit 調(diào)用 minusPrice 方法 ------- this.$store.commit('minusPrice', 2);
mutations: function(state, payload) {
  minusPrice() {
    let newPrice = state.products.forEach(()=>{
      product.price -= payload;
    })
  }
}

// 而 actions 是通過 context.commit() 方法去提交 mutations 內(nèi)的方法, actions 的參數(shù)是 context拉庵,使用的時候通過 dispatch 方法調(diào)用 ------- this.$store.dispatch('minusPriceAsync', 5)
actions:{ // 添加actions
  minusPriceAsync( context, payload ) {
    console.log(context);
    context.commit( 'minusPrice', payload ); //context提交
    // setTimeout( () => {
    //  context.commit( 'minusPrice', payload ); //context提交
    // }, 2000)
  }
}

// 頁面使用:
<template>
    <div id="product-list-two">
        <h2>Product List Two</h2>
        <ul>
            <li v-for="product in products">
                <span class="name">{{ product.name }}</span>
                <span class="price">${{ product.price }}</span>
            </li>
            <button @click="minusPrice">減少價格</button>
            <button @click="minusPriceAsync">異步減少價格</button>
        </ul>
    </div>
</template>

<script>
export default {
    data () {
        return {
            products: this.$store.state.products 
        }
    },
    methods: {
        minusPrice() {
            this.$store.commit('minusPrice', 2)
        },
        minusPriceAsync() {
            this.$store.dispatch('minusPriceAsync', 5); // 分發(fā)actions中的minusPriceAsync這個異步函數(shù)
        }
    }
}
</script>
核心概念5: Modules

由于使用單一狀態(tài)樹灿椅,應(yīng)用的所有狀態(tài)會集中到一個比較大的對象。當(dāng)應(yīng)用變得非常復(fù)雜時名段,store 對象就有可能變得相當(dāng)臃腫阱扬。為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)伸辟。每個模塊擁有自己的 state麻惶、mutation、action信夫、getter窃蹋、甚至是嵌套子模塊——從上至下進(jìn)行同樣方式的分割

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 的狀態(tài)
store.state.b // -> moduleB 的狀態(tài)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市静稻,隨后出現(xiàn)的幾起案子警没,更是在濱河造成了極大的恐慌,老刑警劉巖振湾,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杀迹,死亡現(xiàn)場離奇詭異,居然都是意外死亡押搪,警方通過查閱死者的電腦和手機(jī)树酪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門浅碾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人续语,你說我怎么就攤上這事垂谢。” “怎么了疮茄?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵滥朱,是天一觀的道長。 經(jīng)常有香客問我力试,道長徙邻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任懂版,我火速辦了婚禮鹃栽,結(jié)果婚禮上躏率,老公的妹妹穿的比我還像新娘躯畴。我一直安慰自己,他們只是感情好薇芝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布蓬抄。 她就那樣靜靜地躺著,像睡著了一般夯到。 火紅的嫁衣襯著肌膚如雪嚷缭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天耍贾,我揣著相機(jī)與錄音阅爽,去河邊找鬼。 笑死荐开,一個胖子當(dāng)著我的面吹牛付翁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晃听,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼百侧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了能扒?” 一聲冷哼從身側(cè)響起佣渴,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎初斑,沒想到半個月后辛润,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡见秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年砂竖,在試婚紗的時候發(fā)現(xiàn)自己被綠了灵迫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡晦溪,死狀恐怖瀑粥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情三圆,我是刑警寧澤狞换,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站舟肉,受9級特大地震影響修噪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜路媚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一黄琼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧整慎,春花似錦脏款、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拧揽,卻和暖如春剃盾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背淤袜。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工痒谴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铡羡。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓积蔚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓖墅。 傳聞我的和親對象是個殘疾皇子库倘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

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

  • Vuex是什么? Vuex 是一個專為 Vue.js應(yīng)用程序開發(fā)的狀態(tài)管理模式论矾。它采用集中式存儲管理應(yīng)用的所有組件...
    蕭玄辭閱讀 3,117評論 0 6
  • 目錄 - 1.什么是vuex? - 2.為什么要使用Vuex? - 3.vuex的核心概念教翩?||如何在組件中去使用...
    我跟你蔣閱讀 4,110評論 4 51
  • 安裝 npm npm install vuex --save 在一個模塊化的打包系統(tǒng)中,您必須顯式地通過Vue.u...
    蕭玄辭閱讀 2,937評論 0 7
  • vuex 場景重現(xiàn):一個用戶在注冊頁面注冊了手機(jī)號碼贪壳,跳轉(zhuǎn)到登錄頁面也想拿到這個手機(jī)號碼饱亿,你可以通過vue的組件化...
    sunny519111閱讀 8,015評論 4 111
  • 有個朋友前幾天問了我一個問題。 說有個作家來她學(xué)校做講座,她提了一個問題彪笼,說自己在迷茫期钻注,不知道未來的出路在哪,于...
    洛小婧閱讀 450評論 1 1