原文地址: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%的状况)