原文地址:DATA FLOW IN VUE AND VUEXhtml
原文做者:Benjamin Listwonvue
譯文出自:掘金翻譯計劃git
譯者:linpu.ligithub
校對者:malcolmyu,XatMassacrEvuex
看起來在 Vue 裏面困擾開發者的事情之一是如何在組件之間共享狀態。對於剛剛接觸響應式編程的開發者來講,像Vuex 這種庫,有着繁多的新名詞及其關注點分離的方式,每每使人望而生畏。特別是當你只但願分享一兩個數據片斷時,(這一套邏輯的複雜性)就顯得有點過度了。vue-cli
考慮到這一點的話,我想我應該把兩個簡短的演示放到一塊兒展現出來。第一個經過使用一個簡單的 JavaScript 對象,在每一個新組件當中引用來實現共享狀態。第二個作了和 Vuex 同樣的事情,當它運行成功的時候,也是一個你絕對不該該作的事情的示例(咱們將在最後看看爲何)。編程
你能夠經過查看下面這些演示來開始:json
Using shared objectpromise
Using vuexapp
或者獲取這個倉庫並在本地運行試試看!代碼裏不少地方是2.0版本的特性,但我接下來想講的數據流概念在任何版本里都是相關的,而且它能夠經過一些改變很輕易地向下兼容到1.0。
這些演示都是同樣的功能,只是實現的方法不一樣。應用程序由兩個獨立的聊天組件實例組成。當用戶在一個實例裏提交一個消息的時候,它應該在兩個聊天窗口都出現,由於消息狀態是共享的,下面是一個截圖:
開始前,讓咱們先來看看數據是如何在示例的應用程序當中流轉的。
在這個演示裏,咱們將使用一個簡單的 JavaScript 對象:var store = {...}
,在Client.vue
組件的實例之間共享狀態。下面是關鍵文件的重要代碼部分:
<div id="app"></div> <script> var store = { state: { messages: [] }, newMessage (msg) { this.state.messages.push(msg) } } </script>
這裏有兩個關鍵的地方:
咱們經過把這個對象直接添加到index.html
裏來讓其對整個應用程序可用,也能夠將它注入到應用程序裏更下一層的做用鏈,但目前直接添加顯然更快捷簡單。
咱們在這裏保存狀態,但同時也提供了一個函數來調用它。相比起分散在組件各處的函數,咱們更傾向於讓它們保持在一個地方(便於維護),並在任何須要它們的地方簡單使用。
<template> <div id="app"> <div class="row"> <div class="col"> <client clientid="Client A"></client> </div> <div class="col"> <client clientid="Client B"></client> </div> </div> </div> </template> <script> import Client from './components/Client.vue' export default { components: { Client } } </script>
這裏咱們引入了 Client 組件,並建立了兩個它的實例,使用一個屬性:clientid
,來對每一個實例進行區分。事實上,你應該更動態地去實現這些,但別忘了,目前快捷簡單更重要。
注意一點,到這裏咱們還徹底沒有同步任何狀態。
<template> <div> <h1>{{ clientid }}</h1> <div class="client"> <ul> <li v-for="message in messages"> <label>{{ message.sender }}:</label> {{ message.text }} </li> </ul> <div class="msgbox"> <input v-model="msg" placeholder="Enter a message, then hit [enter]" @keyup.enter="trySendMessage"> </div> </div> </div> </template> <script> export default { data() { return { msg: '', messages: store.state.messages } }, props: ['clientid'], methods: { trySendMessage() { store.newMessage({ text: this.msg, sender: this.clientid }) this.resetMessage() }, resetMessage() { this.msg = '' } } } </script>
下面是應用程序的主要內容:
在該模板裏,設置一個v-for
循環去遍歷messages
集合。
綁定在文本輸入框上的v-model
簡單地存儲了組件的本地數據對象msg
。
一樣在數據對象裏,咱們建立了一個store.state.messages
的引用,它將觸發組件的更新。
最後,將 enter 鍵綁定到trySendMessage
函數,這個函數包含了如下幾個功能:
準備好須要存儲的數據(發送者和消息的一個字典對象)。
調用定義在共享存儲裏的newMessage
函數。
調用一個清理函數:resetMessage
,重置輸入框。一般你更應該在一個promise
完成以後再調用它。
這就是使用對象的方法,來試一試。
好了,如今來試試看用 Vuex 實現。一樣的,先上圖,也便於咱們將 Vuex 的術語(actions,mutations等等)對應到咱們剛剛完成的示例中。
正如你所看到的,Vuex 簡單地形式化了咱們剛剛完成的過程。使用它的時候,所作的事情其實和咱們上面作過的很是像:
建立一個用來共享的存儲,在這個例子中它將經過 vue/vuex 注入到組件當中。
定義組件能夠調用的 actions,它們仍然是集中定義的。
定義實際接觸存儲狀態的 mutations。咱們這麼作,actions 就能夠造成不止一個 mutation,或者執行邏輯去決定調用哪個 mutation。這意味着你不再用擔憂組件當中的業務邏輯了,成功!
當狀態更新時,任何擁有 getter,動態屬性和映射到 store 的組件都會被當即更新。
一樣再來看看代碼:
import store from './vuex/store' new Vue({ // eslint-disable-line no-new el: '#app', render: (h) => h(App), store: store })
此次,咱們用 Vuex 建立了一個存儲並將其直接傳入應用程序當中,替代掉了以前index.html
中的 store
對象。在繼續以前,先來看一下這個存儲:
export default new Vuex.Store({ state: { messages: [] }, actions: { newMessage ({commit}, msg) { commit('NEW_MESSAGE', msg) } }, mutations: { NEW_MESSAGE (state, msg) { state.messages.push(msg) } }, strict: debug })
和咱們本身建立的對象很是類似,可是多了一個mutations
對象。
<div class="row"> <div class="col"> <client clientid="Client A"></client> </div> <div class="col"> <client clientid="Client B"></client> </div> </div>
和上次同樣的配方。(驚人的類似,對吧?)
<script> import { mapState, mapActions } from 'vuex' export default { data() { return { msg: '' } }, props: ['clientid'], computed: { ...mapState({ messages: state => state.messages }) }, methods: { trySendMessage() { this.newMessage({ text: this.msg, sender: this.clientid }) this.resetMessage() }, resetMessage() { this.msg = '' }, ...mapActions(['newMessage']) } } </script>
模板仍然恰好同樣,因此我甚至不須要費心怎麼去引入它。最大的不一樣在於:
使用mapState
來生成對共享消息集合的引用。
使用mapActions
來生成建立一個新消息的動做(action)。
(注意:這些都是 Vuex 2.0特性。)
好的,作完啦!也來看一下這個演示吧。
因此,正如你所但願看到的,本身進行簡單的狀態共享和使用 Vuex 進行共享並無多大區別。而 Vuex 最大的優勢在於它爲你形式化了集中處理數據存儲的過程,並提供了全部功能方法去處理那些數據。
最初,當你閱讀 Vuex 的文檔和示例的時候,它那些針對 mutations,actions 和 modules 的單獨文檔很容易讓人感受困擾。可是若是你勇於跨出那一步,簡單地在store.js
文件裏寫一些關於它們的代碼來開始學習。隨着這個文件的大小增長,你就將找到正確的時間移步到actions.js
裏,或者是把它們更進一步地分離開來。
不要着急,慢慢來,一步一個臺階。固然也可使用vue-cli從建立一個模板開始,我使用browserify模板,並把下面的代碼添加進個人package.json
文件。
"dependencies": { "vue": "^2.0.0-rc.6", "vuex": "^2.0.0-rc.5" }
我知道我還說過要再講一個「很差的」方式。再次,這個演示剛好也是同樣的。很差的地方在於我利用了 Vue 2.0 裏單向綁定的特性來注入回調函數,從而容許了父子模板之間順序的雙向綁定。首先,來看一下2.0文檔中的這個部分,而後再來看看我這個很差的方法。
<div class="row"> <div class="col"> <client clientid="Client A" :messages="messages" :callback="newMessage"></client> </div> <div class="col"> <client clientid="Client B" :messages="messages" :callback="newMessage"></client> </div> </div>
這裏,我在組件上使用了一個屬性將一個動態綁定傳遞到messages
集合裏。可是,我同時還傳遞了一個動做函數,因此能夠在子組件裏調用它。
<script> export default { data() { return { msg: '' } }, props: ['clientid', 'messages', 'callback'], methods: { trySendMessage() { this.callback({ text: this.msg, sender: this.clientid }) this.resetMessage() }, resetMessage() { this.msg = '' } } } </script>
這裏就是很差的作法。
要問爲何有這麼很差嗎?
咱們正在破壞以前圖中所展現的單向循環。
咱們建立了一個在組件及其父組件之間的緊密耦合。
這將變得不可維護。若是你在組件裏須要20個函數,你就將添加20個屬性,管理它們的命名等等,而後,若是任何東西發生改變,呃!
因此爲何還要再展現這段?由於我和其餘人同樣很懶。有時我就會作這樣的事情,僅僅想知道再繼續作下去會有多麼糟糕,而後我就會咒罵本身的懶惰,由於我可能要花上一小時或者一天的時間去清理它們。鑑於這種狀況,我但願我能夠幫助你儘早避免無謂的決定和錯誤,千萬不要傳遞任何你不須要的東西。99%的狀況下,一個單獨的共享狀態已經足夠完美。(不久再詳細講講那1%的狀況)