Vuex基础:教程与释义
Posted 楠倏之语
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vuex基础:教程与释义相关的知识,希望对你有一定的参考价值。
译文地址
更新于2016年11月:这篇文章的代码写于2015年12月,并且使用的是非常老的vuex api版本。
但是,这篇文章仍然很有价值,它深入分析了为什么vuex是如此的重要,vuex的工作原理以及vuex是如何让你的应用更加出色并易于保存。
Vuex是由Vue.js作者创作的一种开发中的原型库,它可以帮助你以一种更加可持续的方式构建大型应用,它的规则类似于Facebook的Flux库(随后被redux等社区迭代)。
相对于直接跳入vuex并开始使用它,在本文中,我会解释为什么vuex在可替代方案中是如此的受欢迎和vuex对你而言有什么价值。
我们要构建的是什么?
我们要构建的简单应用包含了一个按钮和一个计数器。按下按钮会增加计数器。这个任务可以很容易的帮助我们理解以下概念。
在这个应用中有两个组件:
1. 一个按钮(这是事件源)
2. 一个计数器(这里会根据原始事件反映更新)
这两个组件互不相识并且无法通信,哪怕在一些非常小的web应用中,这都是一个非常常见的模型。在大型应用中有大量的组件需要互相通信并且保持彼此连接,以下是一个基本的todo list的流程互动:
这篇文章的目标
我们将使用三种方式来探索解决相同的问题:
1. 使用事件广播以使得组件间通信
2. 使用共享的state对象
3. 使用vuex
读完本文后,希望你能够理解:
1. 如何使用vuex的一个基本的工作流程
2. vuex解决的是什么类型的问题
3. 为什么vuex会优于其他的策略,尽管它有点复杂和严格
建立一个开始点
我们将用3种不同的解决同一个问题,在开始之前,我们需要我有一个相同的开始点,如果你想一道开始,我建议你为本教程建立一个git repo,在开始之后创建一个commit并且为不同方法创建分支。
$ npm install -g vue-cli
$ vue init webpack vuex-tutorial
$ cd vuex-tutorial
$ npm install
$ npm install --save vuex
$ npm run dev
现在你可以看到基础的vue脚手架页面,让我们创建并更新一些文件以使其达到我们想要的效果。
首先,我们在src/components/IncrementButton.vue
中建立一个IncrementButton
组件:
<template>
<button @click="activate">+1</button>
</template>
<script>
export default
methods:
activate ()
console.log('+1 Pressed')
</script>
<style>
</style>
接下来我们创建一个CounterDisplay
组件来展示计数器,让我们在src/components/CounterDisplay.vue
中创建一个新的基础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>
现在你可以使用npm run dev
命令并且在浏览器中打开页面,你会看到一个按钮和一个计数器,点击按钮会在console中输出一个信息,现在我们已经抵达开始点,接下来就是处理过程了。
方法一:事件广播
让我们在组件中进行修改吧。首先,在IncrementButton.vue
中我们使用$dispatch
来发送一个信息给父节点以表明按钮被触发了。
export default
methods:
activate ()
// 发送一个事件给App父节点
this.$dispatch('button-pressed')
在App.vue
中我们由子节点监听事件并重新广播一个新的增量事件给所有节点。
export default
components:
Counter,
Increment
,
events:
'button-pressed': function ()
// 发送一个信息给所有子节点
this.$broadcast('increment')
在CounterDisplay.vue
中我们收听increemnt
事件并增加state的值。
export default
data ()
return
count: 0
,
events:
increment ()
this.count ++
该方法的一些缺点
在本方法中并没有什么技术错误,对于在一个文件中书写你的所有应用逻辑,该方法也是没有技术问题的,但问题是可维护性,以下说明了这种方法是多么的难以维护。
1. 对于每次行为,父组件必须与正确的组件连接并调度事件。
2. 对于一个大型应用很难分析事件是从何发出的。
3. 没有明确的空间给”业务逻辑”,this.count++
在CounterDisplay
中,但业务逻辑可能在任何地方,这就造成了难以维护性。
我来给出这个应用可能引起bug的一个例子:
1. 你聘请了两个实习生:Alice和Bob,你告诉Alice你需要一个新的组件中的计数器,你告诉Bob区写一个Reset按钮。
2. Alice写下一个新的组件FormattedCounterDisplay
并订阅了increment
事件来增加他自己的state,Alice很高兴并提交了代码。
3. Bob写了一个新的”Reset”组件并添加一个reset
事件到App
来重新调度,他在CounterDisplay
实现了reset
以使得计数重置为0,但他不知道Alice的组件也订阅了该计数。
4. 你的用户按下”+1”,发现应用可以正常工作,但当他们按下”reset”,只有一个计数重置了。
这是一个很简单的例子,但它说明了使用事件将分散state和业务逻辑整合在一起时会发生错误。
方法二:共享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. 我们取得store
对象,这是一个不变的对象,但它是其他文件定义的。
2. 在我们的本地data
中我们创建了一个名为sharedState
的新变量,映射到store.state
。
3. vue使得data
反映了store.state
,意味着vue会在store.state
改变时自动更新sharedState
。
至此该工程还没法工作,但现在,我们可以修改IncrementButton.vue
import store from '../store'
export default
data ()
return
sharedState: store.state
,
methods:
activate ()
this.sharedState.counter += 1
- 在这里,我们输入store并添加了它的映射就像之前的例子。
- 当
activate
函数被调用时,经过sharedState
反映到store.state
并增加计数器。 - 所有订阅
counter
的组件和计算属性现在会更新。
这种方法比第一种好在哪
让我们重新回到两位实习生的问题-Alice和Bob。
1. Alice写下FormattedComponentDisplay
并订阅Shared State Counter,这个计数器会始终展示最新的计数结果。
2. Bob的ResetButton
组件设置Shared State Counter为0,这会影响CounterDisplay
和Alice写的FormattedCounterDisplay
。
3. 用户发现reset按钮能够正常工作了。
这种方法还有哪些不足
- 在实习的过程中,Alice和Bob写下更多的计数器展示、reset按钮、以及不同类型的增加按钮,所有这些都会更新同一个共享计数器。Life is good.
- 一旦他们回到学校,你需要维护他们的代码。
- Carol-新来的领导,他要求”我不希望计数器超过100”。
现在你怎么办?
1. 你会找出所有更新计数器的组件?那真令人沮丧。
2. 你想增加一个filter或formatter?那也很麻烦。
现在就出现了问题,业务逻辑分散在应用的各个部分,这个问题在原理上很简单,但对于维护来说就是一个大问题。
一个稍微好一点的方法
现在你重构了所有格原始代码并重写了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
这样做可以使得代码更清晰,你可以明确的调用increment
,并且所有的业务逻辑都存储在其中,但是,一个新的实习生并不知道这些,他发现从该应用的其他部分可以很容易的修改store.state.counter
,这时就很难定位错误了。
之后你又加了很多严格的规则、指南、代码review来确保所有人都使用store.js
中的函数来修改state,当这些都不管用后,你不得不让人力砍掉这个实习生计划。
方法三:Vuex
让我们还原到方法二开始之前的状态,从原理上vuex有点类似方法二,以下是一个有点复杂的图:
我们首先重新新建一个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
我看看代码中发生了什么:
1. 我们获得Vuex
模块并指导Vue
来使用它启动插件。
2. 我们的存储不再是一个问题json对象,而是一个Vuex.Store
的实例。
3. 我们在state中创建一个counter
并设置为0。
4. 我们有一个新的mutations
对象,它有一个INCREMENT
方法,输入一个state并修改这个值。
这段代码中有一些有趣的东西:
1. 所有使用require('../store.js')
或import store from '../store.js'
的代码将由相同的store实例。
2. 我们从不编辑store.state.counter
,但我们得到了这个state的一个可更新和修改的拷贝,这将会非常重要。
现在我们已经解决了store问题,让我们修改IncrementButton.vue
import store from '../store'
export default
methods:
activate ()
store.commit('INCREMENT')
这个组件还没有任何数据,但在点击后会执行store.commit('INCREMENT')
。我们会很快回到这里。
现在,更新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中获得计数。
Vue清楚counter
的computed属性依赖于store.state.counter
,因此当store更新时,它会更新所有的相关项,这就是我们要的!
如果你要重载页面,你会发现计数器正常工作,这里是一步步发生的事情:
1. Vue的事件处理器成为activate
,我们写的这个函数是store.dispatch('INCREMENT')
。
2. 在这里,INCREMENT
是一个行为的名称,它代表“这是一种可改变state的类型”的标示符,同时,我们可以将额外的参数传递给调度函数。
3. Vue能够清楚计数器的调度,同样的,我们也能够让它更加复杂从而服务于更大的应用。
4. 调度器接收state的拷贝并更新它,Vue保存了一份state的旧的拷贝,而这会被用于之后更高级的特征。
5. state更新时,vue自动更新所有依赖于该state的组件。
6. 如果你这样做了,这会让你的代码更具可测试性。
为什么这种方法比方法二更好:
- 如果所有states的一个拷贝存在于开发的整个流程中,vue开发者就可以潜在的构建一个”Time Travelling Debugger”,除了有一个很cool的名字,它还允许你在应用中撤销行为并修改逻辑,提高开发效率。
- 你可以构建一个中间件并使其工作在state改变时,例如,你可以构建一个logger,它可以留存用户的所有行为日志。如果发现了bug,你可以调出日志来查看到底是哪一步出了问题。
- 对比让你自己去监视所有的行为,让你的团队成员都能改变应用的state会变得更加高效。
仍有很多工作要做
这里只展示了vuex的皮毛,现在仅仅只是构建的开始,我相信这种开发模式要发展成熟仍然需要很多年。
你可以发现更多资料去组织你的store,在vuex的文档中学习更多的信息,消化所有的概念可能需要一些时间,我们可能也要做出一些错误的尝试才能找出正确的方法。
结语:处理实习生的代码
你可以移植你的应用到vue.js,你的实习生发现可以在他的组件中很便捷的写入store.state.counter
,你现在已经拿到了最后一根稻草,现在在你的store.js
中加一行。
var store = new Vuex.Store(
state:
counter: 0
,
mutations:
INCREMENT (state)
state.counter ++
,
strict: true // Vuex申请专利的反实习装置
)
现在所有人可以直接写入store,这也会引起一个问题,注意这会降低你的应用的速率,你可以在产品发布时安全的去掉它,去看看文档中例子是怎么做的。
以上是关于Vuex基础:教程与释义的主要内容,如果未能解决你的问题,请参考以下文章
[4G/5G/6G专题基础-150]: 6G总体愿景与潜在关键技术白皮书解读-5-缩略语释义