[譯] VUE 和 VUEX 中的數據流

看起來在 Vue 裏面困擾開發者的事情之一是如何在組件之間共享狀態。對於剛剛接觸響應式編程的開發者來講,像Vuex 這種庫,有着繁多的新名詞及其關注點分離的方式,每每使人望而生畏。特別是當你只但願分享一兩個數據片斷時,(這一套邏輯的複雜性)就顯得有點過度了。vue-cli

考慮到這一點的話,我想我應該把兩個簡短的演示放到一塊兒展現出來。第一個經過使用一個簡單的 JavaScript 對象,在每一個新組件當中引用來實現共享狀態。第二個作了和 Vuex 同樣的事情,當它運行成功的時候,也是一個你絕對不該該作的事情的示例(咱們將在最後看看爲何)。編程

你能夠經過查看下面這些演示來開始:json

或者獲取這個倉庫並在本地運行試試看!代碼裏不少地方是2.0版本的特性,但我接下來想講的數據流概念在任何版本里都是相關的,而且它能夠經過一些改變很輕易地向下兼容到1.0。

這些演示都是同樣的功能,只是實現的方法不一樣。應用程序由兩個獨立的聊天組件實例組成。當用戶在一個實例裏提交一個消息的時候,它應該在兩個聊天窗口都出現,由於消息狀態是共享的,下面是一個截圖:

用一個對象共享狀態

開始前,讓咱們先來看看數據是如何在示例的應用程序當中流轉的。

在這個演示裏,咱們將使用一個簡單的 JavaScript 對象:var store = {...},在Client.vue組件的實例之間共享狀態。下面是關鍵文件的重要代碼部分:

index.html
<div id="app"></div>
<script>
  var store = {
    state: {
      messages: []
    },
    newMessage (msg) {
      this.state.messages.push(msg)
    }
  }
</script>

這裏有兩個關鍵的地方:

  1. 咱們經過把這個對象直接添加到index.html裏來讓其對整個應用程序可用,也能夠將它注入到應用程序裏更下一層的做用鏈,但目前直接添加顯然更快捷簡單。

  2. 咱們在這裏保存狀態,但同時也提供了一個函數來調用它。相比起分散在組件各處的函數,咱們更傾向於讓它們保持在一個地方(便於維護),並在任何須要它們的地方簡單使用。

App.vue
<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,來對每一個實例進行區分。事實上,你應該更動態地去實現這些,但別忘了,目前快捷簡單更重要。

注意一點,到這裏咱們還徹底沒有同步任何狀態。

Client.vue
<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>

下面是應用程序的主要內容:

  1. 在該模板裏,設置一個v-for循環去遍歷messages集合。

  2. 綁定在文本輸入框上的v-model簡單地存儲了組件的本地數據對象msg

  3. 一樣在數據對象裏,咱們建立了一個store.state.messages的引用,它將觸發組件的更新。

  4. 最後,將 enter 鍵綁定到trySendMessage函數,這個函數包含了如下幾個功能:

    1. 準備好須要存儲的數據(發送者和消息的一個字典對象)。

    2. 調用定義在共享存儲裏的newMessage函數。

    3. 調用一個清理函數:resetMessage,重置輸入框。一般你更應該在一個promise完成以後再調用它。

這就是使用對象的方法,來試一試

用 Vuex 共享狀態

好了,如今來試試看用 Vuex 實現。一樣的,先上圖,也便於咱們將 Vuex 的術語(actions,mutations等等)對應到咱們剛剛完成的示例中。

正如你所看到的,Vuex 簡單地形式化了咱們剛剛完成的過程。使用它的時候,所作的事情其實和咱們上面作過的很是像:

  1. 建立一個用來共享的存儲,在這個例子中它將經過 vue/vuex 注入到組件當中。

  2. 定義組件能夠調用的 actions,它們仍然是集中定義的。

  3. 定義實際接觸存儲狀態的 mutations。咱們這麼作,actions 就能夠造成不止一個 mutation,或者執行邏輯去決定調用哪個 mutation。這意味着你不再用擔憂組件當中的業務邏輯了,成功!

  4. 當狀態更新時,任何擁有 getter,動態屬性和映射到 store 的組件都會被當即更新。

一樣再來看看代碼:

main.js
import store from './vuex/store'

new Vue({ // eslint-disable-line no-new
  el: '#app',
  render: (h) => h(App),
  store: store
})

此次,咱們用 Vuex 建立了一個存儲並將其直接傳入應用程序當中,替代掉了以前index.html中的 store 對象。在繼續以前,先來看一下這個存儲:

store.js
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對象。

Client.vue
<div class="row">
  <div class="col">
    <client clientid="Client A"></client>
  </div>
  <div class="col">
    <client clientid="Client B"></client>
  </div>
</div>

和上次同樣的配方。(驚人的類似,對吧?)

Client.vue
<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>

模板仍然恰好同樣,因此我甚至不須要費心怎麼去引入它。最大的不一樣在於:

  1. 使用mapState來生成對共享消息集合的引用。

  2. 使用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文檔中的這個部分,而後再來看看我這個很差的方法。

App.vue
<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集合裏。可是,我同時還傳遞了一個動做函數,因此能夠在子組件裏調用它。

Client.vue
<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>

這裏就是很差的作法。

要問爲何有這麼很差嗎?

  1. 咱們正在破壞以前圖中所展現的單向循環。

  2. 咱們建立了一個在組件及其父組件之間的緊密耦合。

  3. 這將變得不可維護。若是你在組件裏須要20個函數,你就將添加20個屬性,管理它們的命名等等,而後,若是任何東西發生改變,呃!

因此爲何還要再展現這段?由於我和其餘人同樣很懶。有時我就會作這樣的事情,僅僅想知道再繼續作下去會有多麼糟糕,而後我就會咒罵本身的懶惰,由於我可能要花上一小時或者一天的時間去清理它們。鑑於這種狀況,我但願我能夠幫助你儘早避免無謂的決定和錯誤,千萬不要傳遞任何你不須要的東西。99%的狀況下,一個單獨的共享狀態已經足夠完美。(不久再詳細講講那1%的狀況)

相關文章
相關標籤/搜索