Vuex基礎(chǔ):教程與釋義

譯文地址

Vuex basics: Tutorial and explanation

更新于2016年11月:這篇文章的代碼寫于2015年12月豌拙,并且使用的是非常老的vuex api版本。
但是蒜焊,這篇文章仍然很有價值匀谣,它深入分析了為什么vuex是如此的重要,vuex的工作原理以及vuex是如何讓你的應(yīng)用更加出色并易于保存武翎。

Vuex是由Vue.js作者創(chuàng)作的一種開發(fā)中的原型庫烈炭,它可以幫助你以一種更加可持續(xù)的方式構(gòu)建大型應(yīng)用,它的規(guī)則類似于Facebook的Flux庫(隨后被redux等社區(qū)迭代)宝恶。
相對于直接跳入vuex并開始使用它符隙,在本文中,我會解釋為什么vuex在可替代方案中是如此的受歡迎和vuex對你而言有什么價值垫毙。

我們要構(gòu)建的是什么霹疫?


我們要構(gòu)建的簡單應(yīng)用包含了一個按鈕和一個計數(shù)器。按下按鈕會增加計數(shù)器综芥。這個任務(wù)可以很容易的幫助我們理解以下概念丽蝎。



在這個應(yīng)用中有兩個組件:

  1. 一個按鈕(這是事件源)
  2. 一個計數(shù)器(這里會根據(jù)原始事件反映更新)

這兩個組件互不相識并且無法通信,哪怕在一些非常小的web應(yīng)用中膀藐,這都是一個非常常見的模型屠阻。在大型應(yīng)用中有大量的組件需要互相通信并且保持彼此連接,以下是一個基本的todo list的流程互動:

這篇文章的目標(biāo)

我們將使用三種方式來探索解決相同的問題:

  1. 使用事件廣播以使得組件間通信
  2. 使用共享的state對象
  3. 使用vuex

讀完本文后额各,希望你能夠理解:

  1. 如何使用vuex的一個基本的工作流程
  2. vuex解決的是什么類型的問題
  3. 為什么vuex會優(yōu)于其他的策略国觉,盡管它有點(diǎn)復(fù)雜和嚴(yán)格

建立一個開始點(diǎn)

我們將用3種不同的解決同一個問題,在開始之前臊泰,我們需要我有一個相同的開始點(diǎn)蛉加,如果你想一道開始,我建議你為本教程建立一個git repo缸逃,在開始之后創(chuàng)建一個commit并且為不同方法創(chuàng)建分支针饥。

$ npm install -g vue-cli
$ vue init webpack vuex-tutorial
$ cd vuex-tutorial
$ npm install
$ npm install --save vuex
$ npm run dev

現(xiàn)在你可以看到基礎(chǔ)的vue腳手架頁面,讓我們創(chuàng)建并更新一些文件以使其達(dá)到我們想要的效果需频。
首先丁眼,我們在src/components/IncrementButton.vue中建立一個IncrementButton組件:

<template>
  <button @click="activate">+1</button>
</template>

<script>
export default {
  methods: {
    activate () {
      console.log('+1 Pressed')
    }
  }
}
</script>

<style>
</style>

接下來我們創(chuàng)建一個CounterDisplay組件來展示計數(shù)器,讓我們在src/components/CounterDisplay.vue中創(chuàng)建一個新的基礎(chǔ)vue組件昭殉。

<template>
  Count is {{ count }}
</template>

<script>
export default {
  data () {
    return {
      count: 0
    }
  }
}
</script>
<style>
</style>

替換App.vue文件:

<template>
  <div id="app">
    <h3>Increment:</h3>
    <increment></increment>
    <h3>Counter:</h3>
    <counter></counter>
  </div>
</template>

<script>
import Counter from './components/CounterDisplay.vue'
import Increment from './components/IncrementButton.vue'
export default {
  components: {
    Counter,
    Increment
  }
}
</script>

<style>
</style>

現(xiàn)在你可以使用npm run dev命令并且在瀏覽器中打開頁面苞七,你會看到一個按鈕和一個計數(shù)器藐守,點(diǎn)擊按鈕會在console中輸出一個信息,現(xiàn)在我們已經(jīng)抵達(dá)開始點(diǎn)蹂风,接下來就是處理過程了卢厂。

方法一:事件廣播


讓我們在組件中進(jìn)行修改吧。首先惠啄,在IncrementButton.vue中我們使用$dispatch來發(fā)送一個信息給父節(jié)點(diǎn)以表明按鈕被觸發(fā)了慎恒。

export default {
  methods: {
    activate () {
      // 發(fā)送一個事件給App父節(jié)點(diǎn)
      this.$dispatch('button-pressed')
    }
  }
}

App.vue中我們由子節(jié)點(diǎn)監(jiān)聽事件并重新廣播一個新的增量事件給所有節(jié)點(diǎn)。

export default {
  components: {
    Counter,
    Increment
  },
  events: {
    'button-pressed': function () {
      // 發(fā)送一個信息給所有子節(jié)點(diǎn)
      this.$broadcast('increment')
    }
  }
}

CounterDisplay.vue中我們收聽increemnt事件并增加state的值撵渡。

export default {
  data () {
    return {
      count: 0
    }
  },
  events: {
    increment () {
      this.count ++
    }
  }
}
該方法的一些缺點(diǎn)

在本方法中并沒有什么技術(shù)錯誤融柬,對于在一個文件中書寫你的所有應(yīng)用邏輯,該方法也是沒有技術(shù)問題的趋距,但問題是可維護(hù)性粒氧,以下說明了這種方法是多么的難以維護(hù)。

  1. 對于每次行為节腐,父組件必須與正確的組件連接并調(diào)度事件外盯。
  2. 對于一個大型應(yīng)用很難分析事件是從何發(fā)出的。
  3. 沒有明確的空間給"業(yè)務(wù)邏輯"铜跑,this.count++CounterDisplay中门怪,但業(yè)務(wù)邏輯可能在任何地方,這就造成了難以維護(hù)性锅纺。

我來給出這個應(yīng)用可能引起bug的一個例子:

  1. 你聘請了兩個實習(xí)生:Alice和Bob,你告訴Alice你需要一個新的組件中的計數(shù)器肋殴,你告訴Bob區(qū)寫一個Reset按鈕囤锉。
  2. Alice寫下一個新的組件FormattedCounterDisplay并訂閱了increment事件來增加他自己的state,Alice很高興并提交了代碼护锤。
  3. Bob寫了一個新的"Reset"組件并添加一個reset事件到App來重新調(diào)度官地,他在CounterDisplay實現(xiàn)了reset以使得計數(shù)重置為0,但他不知道Alice的組件也訂閱了該計數(shù)烙懦。
  4. 你的用戶按下"+1"驱入,發(fā)現(xiàn)應(yīng)用可以正常工作,但當(dāng)他們按下"reset"氯析,只有一個計數(shù)重置了亏较。

這是一個很簡單的例子,但它說明了使用事件將分散state和業(yè)務(wù)邏輯整合在一起時會發(fā)生錯誤掩缓。

方法二:共享state

讓我們還原到方法一開始前雪情,我們新建一個新的文件src/store.js

export default {
  state: {
    counter: 0
  }
}

我們第一次修改CounterDisplay.vue

<template>
  Count is {{ sharedState.counter }}
</template>

<script>
import store from '../store'

export default {
  data () {
    return {
      sharedState: store.state
    }
  }
}
</script>

我在在這個做了一些有趣的事情:

  1. 我們?nèi)〉?code>store對象,這是一個不變的對象你辣,但它是其他文件定義的巡通。
  2. 在我們的本地data中我們創(chuàng)建了一個名為sharedState的新變量尘执,映射到store.state
  3. vue使得data反映了store.state宴凉,意味著vue會在store.state改變時自動更新sharedState誊锭。

至此該工程還沒法工作,但現(xiàn)在弥锄,我們可以修改IncrementButton.vue

import store from '../store'

export default {
  data () {
    return {
      sharedState: store.state
    }
  },
  methods: {
    activate () {
      this.sharedState.counter += 1
    }
  }
}
  1. 在這里丧靡,我們輸入store并添加了它的映射就像之前的例子。
  2. 當(dāng)activate函數(shù)被調(diào)用時叉讥,經(jīng)過sharedState反映到store.state并增加計數(shù)器窘行。
  3. 所有訂閱counter的組件和計算屬性現(xiàn)在會更新。
這種方法比第一種好在哪

讓我們重新回到兩位實習(xí)生的問題-Alice和Bob图仓。

  1. Alice寫下FormattedComponentDisplay并訂閱Shared State Counter罐盔,這個計數(shù)器會始終展示最新的計數(shù)結(jié)果。
  2. Bob的ResetButton組件設(shè)置Shared State Counter為0救崔,這會影響CounterDisplay和Alice寫的FormattedCounterDisplay惶看。
  3. 用戶發(fā)現(xiàn)reset按鈕能夠正常工作了。
這種方法還有哪些不足
  1. 在實習(xí)的過程中六孵,Alice和Bob寫下更多的計數(shù)器展示纬黎、reset按鈕、以及不同類型的增加按鈕劫窒,所有這些都會更新同一個共享計數(shù)器本今。Life is good.
  2. 一旦他們回到學(xué)校,你需要維護(hù)他們的代碼主巍。
  3. Carol-新來的領(lǐng)導(dǎo)冠息,他要求"我不希望計數(shù)器超過100"。

現(xiàn)在你怎么辦孕索?

  1. 你會找出所有更新計數(shù)器的組件逛艰?那真令人沮喪。
  2. 你想增加一個filter或formatter搞旭?那也很麻煩散怖。

現(xiàn)在就出現(xiàn)了問題,業(yè)務(wù)邏輯分散在應(yīng)用的各個部分肄渗,這個問題在原理上很簡單镇眷,但對于維護(hù)來說就是一個大問題。

一個稍微好一點(diǎn)的方法

現(xiàn)在你重構(gòu)了所有格原始代碼并重寫了store.js

var store = {
  state: {
    counter: 0
  },
  increment: function () {
    if (store.state.counter < 100) {
      store.state.counter += 1;
    }
  },
  reset: function () {
    store.state.counter = 0;
  }
}

export default store

這樣做可以使得代碼更清晰恳啥,你可以明確的調(diào)用increment偏灿,并且所有的業(yè)務(wù)邏輯都存儲在其中,但是钝的,一個新的實習(xí)生并不知道這些翁垂,他發(fā)現(xiàn)從該應(yīng)用的其他部分可以很容易的修改store.state.counter铆遭,這時就很難定位錯誤了。
之后你又加了很多嚴(yán)格的規(guī)則沿猜、指南枚荣、代碼review來確保所有人都使用store.js中的函數(shù)來修改state,當(dāng)這些都不管用后啼肩,你不得不讓人力砍掉這個實習(xí)生計劃橄妆。

方法三:Vuex

讓我們還原到方法二開始之前的狀態(tài),從原理上vuex有點(diǎn)類似方法二祈坠,以下是一個有點(diǎn)復(fù)雜的圖:


我們首先重新新建一個src/store.js害碾,這次我們的代碼改為:

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

var store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    INCREMENT (state) {
      state.counter ++
    }
  }
})

export default store

我看看代碼中發(fā)生了什么:

  1. 我們獲得Vuex模塊并指導(dǎo)Vue來使用它啟動插件。
  2. 我們的存儲不再是一個問題json對象赦拘,而是一個Vuex.Store的實例慌随。
  3. 我們在state中創(chuàng)建一個counter
    并設(shè)置為0。
  4. 我們有一個新的mutations對象躺同,它有一個INCREMENT方法阁猜,輸入一個state并修改這個值。

這段代碼中有一些有趣的東西:

  1. 所有使用require('../store.js')import store from '../store.js'的代碼將由相同的store實例蹋艺。
  2. 我們從不編輯store.state.counter剃袍,但我們得到了這個state的一個可更新和修改的拷貝,這將會非常重要捎谨。

現(xiàn)在我們已經(jīng)解決了store問題民效,讓我們修改IncrementButton.vue

import store from '../store'

export default {
  methods: {
    activate () {
      store.commit('INCREMENT')
    }
  }
}

這個組件還沒有任何數(shù)據(jù),但在點(diǎn)擊后會執(zhí)行store.commit('INCREMENT')涛救。我們會很快回到這里研铆。
現(xiàn)在,更新CounterDisplay.vue

<template>
  Count is {{ counter }}
</template>

<script>
import store from '../store'

export default {
  computed: {
    counter () {
      return store.state.counter
    }
  }
}
</script>

這時事情變得有趣了州叠,我們不再訂閱共享state咧栗,我們使用vue的computed屬性從store中獲得計數(shù)。
Vue清楚counter的computed屬性依賴于store.state.counter致板,因此當(dāng)store更新時,它會更新所有的相關(guān)項斟或,這就是我們要的!
如果你要重載頁面萝挤,你會發(fā)現(xiàn)計數(shù)器正常工作御毅,這里是一步步發(fā)生的事情:

  1. Vue的事件處理器成為activate怜珍,我們寫的這個函數(shù)是store.dispatch('INCREMENT')端蛆。
  2. 在這里,INCREMENT是一個行為的名稱酥泛,它代表“這是一種可改變state的類型”的標(biāo)示符今豆,同時,我們可以將額外的參數(shù)傳遞給調(diào)度函數(shù)柔袁。
  3. Vue能夠清楚計數(shù)器的調(diào)度呆躲,同樣的,我們也能夠讓它更加復(fù)雜從而服務(wù)于更大的應(yīng)用捶索。
  4. 調(diào)度器接收state的拷貝并更新它插掂,Vue保存了一份state的舊的拷貝,而這會被用于之后更高級的特征情组。
  5. state更新時燥筷,vue自動更新所有依賴于該state的組件。
  6. 如果你這樣做了院崇,這會讓你的代碼更具可測試性肆氓。

為什么這種方法比方法二更好:

  1. 如果所有states的一個拷貝存在于開發(fā)的整個流程中,vue開發(fā)者就可以潛在的構(gòu)建一個"Time Travelling Debugger"底瓣,除了有一個很cool的名字谢揪,它還允許你在應(yīng)用中撤銷行為并修改邏輯,提高開發(fā)效率捐凭。
  2. 你可以構(gòu)建一個中間件并使其工作在state改變時拨扶,例如,你可以構(gòu)建一個logger茁肠,它可以留存用戶的所有行為日志患民。如果發(fā)現(xiàn)了bug,你可以調(diào)出日志來查看到底是哪一步出了問題垦梆。
  3. 對比讓你自己去監(jiān)視所有的行為匹颤,讓你的團(tuán)隊成員都能改變應(yīng)用的state會變得更加高效。

仍有很多工作要做

這里只展示了vuex的皮毛托猩,現(xiàn)在僅僅只是構(gòu)建的開始印蓖,我相信這種開發(fā)模式要發(fā)展成熟仍然需要很多年。
你可以發(fā)現(xiàn)更多資料去組織你的store京腥,在vuex的文檔中學(xué)習(xí)更多的信息赦肃,消化所有的概念可能需要一些時間,我們可能也要做出一些錯誤的嘗試才能找出正確的方法。

結(jié)語:處理實習(xí)生的代碼

你可以移植你的應(yīng)用到vue.js他宛,你的實習(xí)生發(fā)現(xiàn)可以在他的組件中很便捷的寫入store.state.counter船侧,你現(xiàn)在已經(jīng)拿到了最后一根稻草,現(xiàn)在在你的store.js中加一行勺爱。

var store = new Vuex.Store({
  state: {
    counter: 0
  },
  mutations: {
    INCREMENT (state) {
      state.counter ++
    }
  },
  strict: true // Vuex申請專利的反實習(xí)裝置
})

現(xiàn)在所有人可以直接寫入store琐鲁,這也會引起一個問題围段,注意這會降低你的應(yīng)用的速率投放,你可以在產(chǎn)品發(fā)布時安全的去掉它,去看看文檔中例子是怎么做的涝桅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冯遂,一起剝皮案震驚了整個濱河市蛤肌,隨后出現(xiàn)的幾起案子裸准,更是在濱河造成了極大的恐慌炒俱,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惊完,居然都是意外死亡小槐,警方通過查閱死者的電腦和手機(jī)凿跳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門茧彤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曾掂,“玉大人,你說我怎么就攤上這事珠洗⌒肀停” “怎么了膊爪?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵米酬,是天一觀的道長淮逻。 經(jīng)常有香客問我爬早,道長启妹,這世上最難降的妖魔是什么饶米? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任照瘾,我火速辦了婚禮丧慈,結(jié)果婚禮上主卫,老公的妹妹穿的比我還像新娘簇搅。我一直安慰自己瘩将,他們只是感情好姿现,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布建钥。 她就那樣靜靜地躺著虐沥,像睡著了一般欲险。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槐壳,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天,我揣著相機(jī)與錄音带兜,去河邊找鬼。 笑死刑巧,一個胖子當(dāng)著我的面吹牛啊楚,可吹牛的內(nèi)容都是我干的恭理。 我是一名探鬼主播蚯斯,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拍嵌,長吁一口氣:“原來是場噩夢啊……” “哼横辆!你這毒婦竟也來了狈蚤?” 一聲冷哼從身側(cè)響起脆侮,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤靖避,失蹤者是張志新(化名)和其女友劉穎幻捏,沒想到半個月后篡九,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榛臼,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了路呜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胀葱。...
    茶點(diǎn)故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡抵屿,死狀恐怖搂抒,靈堂內(nèi)的尸體忽然破棺而出尿扯,到底是詐尸還是另有隱情衷笋,我是刑警寧澤爵赵,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站泊脐,受9級特大地震影響空幻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜容客,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一秕铛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耘柱,春花似錦如捅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至士袄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冷蚂。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓彻亲,卻偏偏與公主長得像宦芦,于是被迫代替她去往敵國和親大咱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評論 2 359

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