在本系列的前一篇文章中,我们讨论了什么是商店,为什么它是有用的,以及什么时候是实施商店的好时机。考虑到这些原则,我们现在可以研究一下如何用Vuex实现一个存储。

Vuex是Vue.js的官方存储库和状态管理模式。它的优势在于它是Vue的第一个存储库,因此已经被全世界成千上万的开发者用于成千上万的项目中,并且经过时间的检验是有效的。让我们来看看它是如何工作的。
要注意的是,如果你使用的是Vue 2,你会想使用Vuex 3。我将在Vuex 4中展示例子,它与Vue 3兼容,但只有一些区别,所以这里描述的大多数东西都适用。
安装和设置
为了开始使用Vuex,你可以用npm或yarn安装它。
npm install [email protected] --save
# or with yarn
yarn add [email protected]
然后通过一个类似于Vue 3的createApp() 功能的createStore() 函数将其实例化。
// store/index.js
import {createStore} from 'vuex'
export default createStore()
最后,你可以像其他Vue插件一样通过use() 方法在Vue上注册它。
// main.js
import { createApp } from 'vue'
import store from '@/store' // short for @/store/index
const app = createApp({ /* your root component */ })
app.use(store)
商店的定义
Vuex中的商店是通过传递给createStore函数的一个对象来定义的。该对象可以有以下任何属性:状态、getters、mutations和actions。
// store/index.js
export default createStore({
state:{},
getters:{},
mutations: {},
actions:{}
})
状态
商店的状态是在state属性中定义的。状态只是一个花哨的词,意思是你想保留在你的商店中的数据。你可以把它看作是一个组件的数据属性,但对你的任何组件都是可用的。你可以把你想要的任何类型的数据放在这里,比如我们在上一篇文章中提到的登录的用户。另外在状态中定义的不同属性可以是你想要的任何数据类型,比如对象、数组、字符串、数字等。
state:{
user: { name: 'John Doe', email: '[email protected]', username: 'jd123'},
posts: [],
someString: 'etc'
}
为了在任何组件模板中访问商店的状态,你可以使用$store.state[propertyNameHere] 。例如,为了在一个配置文件组件中访问用户的名字,我们要做以下工作。
// ProfileComponent.vue
<template>
<h1>Hello, my name is {{$store.state.user.name}}</h1>
</template>
或者我们可以通过使用一个计算属性来清理一下模板。
// ProfileComponent.vue
<template>
<h1>Hello, my name is {{name}}</h1>
</template>
<script>
export default{
computed:{
name(){ return this.$store.user.name }
}
}
</script>
当你继续从你的组件中访问商店的状态时,以这种方式制作的计算属性会变得越来越冗长。为了简化事情,你可以使用Vuex的一个辅助函数mapState ,把我们想在组件中访问的状态的顶级属性数组传给它。
<template>
<h1>Hello, my name is {{user.name}}</h1>
</template>
<script>
export default{
computed:{
...mapState(['user'])
}
}
</script>
mapState Vuex函数甚至比这更灵活,但这通常足以满足我90%的使用情况。
获取器
除了存储在状态中的数据本身,Vuex存储也可以有所谓的 "getters"。你可以把getters看作是存储版的计算属性,就像计算属性一样,它们会根据依赖关系进行缓存。Vuex中的所有getters都是定义在 "getters "属性下的函数,并接收状态以及所有其他getters作为参数。然后从函数中返回的东西就是该getter的值。
{
state:{
posts: ['post 1', 'post 2', 'post 3', 'post 4']
},
// the result from all the postsCount getters below is exactly the same
// personal preference dicates how you'd like to write them
getters:{
// arrow function
postsCount: state => state.posts.length,
// traditional function
postsCount: function(state){
return state.posts.length
},
// method shorthand
postsCount(state){
return state.posts.length
},
// can access other getters
postsCountMessage: (state, getters) => `${getters.postsCount} posts available`
}
}
除了在getters属性下而不是在state属性下寻找之外,访问商店的getters与访问state是一样的。
// FeedComponent.vue
<template>
<p>{{$store.getters.postsCount}} posts available</p>
</template>
你也可以在你的组件中使用一个计算属性或一个辅助函数(这次是mapGetters ),就像对待状态一样。
// FeedComponent.vue
<template>
<p>{{postsCount}} posts available</p>
</template>
<script>
import {mapGetters} from 'vuex'
export default{
computed:{
...mapGetters(['postsCount'])
}
}
</script>
变异和行动
如果你还记得本系列的前一篇文章,商店的一个定义原则是有关于如何改变商店数据的规则。Vuex的状态管理模式将这个责任分配给了动作和突变。Vuex是本系列讨论的解决方案中唯一一个对这两个概念进行了区分的。
这就是它的简短和甜蜜。
- 突变总是同步的,是唯一被允许直接改变状态的东西。一个突变只负责改变单一的数据。
- 行动可以是同步的或异步的,不应该直接改变状态,而是调用突变。行动可以调用多个突变。
此外,这里还有一些更多的信息和最佳实践。
- 变异
- 惯例是大写突变名称
- 通过调用
commit('MUTATION_NAME', payload) - 有效载荷是可选的
- 不应包含任何关于是否突变数据的逻辑
- 最好的做法是只从你的动作中调用它们(即使你有机会在组件中调用它们)。
- 行动
- 可以从商店的其他动作中调用
- 是在组件中改变状态的主要方式
- 通过调用
dispatch('actionName', payload) - 有效载荷是可选的
- 可以包含改变或不改变的逻辑
- 良好的做法是一开始就定义为异步,这样,如果逻辑从同步变为异步,你调度动作的地方就不必改变。
下面是一个定义动作和突变的例子。
{
state: {
posts: ['post 1', 'post 2', 'post 3', 'post 4'],
user: { postsCount: 2 }
errors: []
}
mutations:{
// convention to uppercase mutation names
INSERT_POST(state, post){
state.posts.push(post)
},
INSERT_ERROR(state, error){
state.errors.push(error)
},
INCREMENT_USER_POSTS_COUNT(state, error){
state.user.postsCount++
}
},
actions:{
async insertPost({commit}, payload){
//make some kind of ajax request
try{
await doAjaxRequest(payload)
// can commit multiple mutations in an action
commit('INSERT_POST', payload)
commit('INCREMENT_USER_POSTS_COUNT')
}catch(error){
commit('INSERT_ERROR', error)
}
}
}
}
这些突变可能为了说明问题而显得过于简化,但在生产代码库中,突变应该同样短小精悍,因为它们只负责更新单一的状态。
如果你是Vuex的新手,突变和动作之间的区别一开始可能有点麻烦,但过一段时间你就会习惯了。你不会真的喜欢它,但你会习惯它。尽管编写突变并不是最令人愉快的,因为它产生了大量的模板,但对于devtools的时间旅行经验来说,调试你的应用程序是必要的。然而,Vuex 5的RFC表明,在未来,突变将不再是必要的,这样我们就可以跳过突变的模板,仍然享受devtools的体验。
最后,为了从组件中运行动作,你可以调用存储区的dispatch方法,并将你想要运行的动作名称作为第一个参数传递给它,将有效载荷作为第二个参数。
// PostEditorComponent.vue
<template>
<input type="text" v-model="post" />
<button @click="$store.dispatch('insertPost', post)">Save</button>
</template>
从一个组件的方法中调度你的动作也是很常见的。
// PostEditorComponent.vue
<template>
<input type="text" v-model="post" />
<button @click="savePost">Save</button>
</template>
<script>
export default{
methods:{
savePost(){
this.$store.dispatch('insertPost', this.post)
}
}
}
</script>
就像状态和获取器一样,你可以使用辅助函数将动作映射到组件方法。
// PostEditorComponent.vue
<template>
<input type="text" v-model="post" />
<button @click="insertPost(post)">Save</button>
</template>
<script>
import {mapActions} from 'vuex'
export default{
methods:{
...mapActions(['insertPost'])
}
}
</script>
在模块中进行组织
随着你的应用程序的增长,你会发现一个单一的全局存储文件变得令人难以忍受的冗长和难以浏览。Vuex的解决方案叫做模块,它允许你根据领域逻辑将你的商店划分为不同的文件(比如一个文件用于帖子,另一个用于用户,另一个用于产品,等等),然后在一个特定的命名空间下从模块中访问状态、getters等。
你可以在Vuex的官方文档中看到模块的具体实现细节。可以说,在Vue学校,我们建议从一开始就实现模块,因为从一级存储到分解成模块的存储的重构会有点痛苦。另外,我个人对模块的经验是,虽然在大型应用中比单层存储有明显的改进,但其语法有点麻烦。
Vue开发工具
最后,如果我不强调Vuex与Vue Devtools的关系有多好,那就是我的失职。对于使用Vuex 3的Vue 2项目,你在devtools中有一个专门的Vuex标签,允许你看到一个正在运行的突变列表,时间旅行到一个特定的突变,看看你的应用程序和状态在那个时间点是什么样子,甚至可以将应用程序恢复到特定突变的状态。
Vuex的devtools集成对于一个项目来说就是版本控制,对于你的应用程序的状态来说也是如此(尽管是在一个更临时的意义上)。你可以在Vue学校的这个免费课程中看到devtools在Vuex中的作用。
截至2021年5月,你那些使用Vuex 4的闪亮的新Vue 3项目不会在devtools中支持Vuex,因为集成还没有完成,但它肯定在维护者的todos列表中,这只是一个时间问题而已。
值得注意的功能
为了对Vuex进行总结,让我们快速回顾一下它最值得注意的功能,以帮助你决定实施最适合你和你的项目的商店。
Vuex。
- 是官方的解决方案
- 是历史最悠久、最经得起考验的解决方案
- 在Vue社区很受欢迎,有大量的教育资源可供利用(因此可能最适合让新开发人员加入项目)
- 与Vue Devtools完美集成,提供了良好的开发体验。
总结
Vuex是一个经过战斗考验的、受欢迎的Vue.js商店,但不是你唯一的选择。对于轻量级的应用程序,它可能是矫枉过正,增加了不必要的复杂性。在下一课中,我们将讨论Vue.js商店的新孩子。Pinia。