Vuex 是在 Vue.js 应用程序中管理大型复杂状态的理想工具。但是当代码库变得庞大时,在不使用辅助程序和模块等附加功能的情况下,将 Vue组件与基本 Vuex 拼接在一起可能会开始感到混乱。
本教程将介绍这些有用的工具,可以简化您的 Vuex 设置。让我们看看如何将它们应用于现有的 Vuex 驱动的应用程序。
示例应用程序
我们已经将代码上传到 Github: https : //github.com/Code-Pop/Vuex_Fundamentals.git
您可以将其克隆到本地:
git clone https://github.com/Code-Pop/Vuex_Fundamentals.gitcd Vuex_Fundamentals
L5 端分支是我们从这里开始的分支。
git checkout L5-end
在我们进入代码之前,让我们先运行应用程序并熟悉它的作用。
首先运行npm install
以安装所有依赖项:
npm install
然后我们可以启动服务器:
npm run serve
在浏览器中访问localhost:8080,您可以尝试使用示例应用程序。
该应用程序包含三个页面:
-
Events:显示事件列表的页面(如简单的 Meetup.com)
-
关于:显示一些描述应用程序的文本的页面
-
创建事件:带有用于创建新事件的表单的页面
现在,让我们来看看代码。我们的主要组件托管在src/views文件夹中。
本教程的重点是 Vue 组件和 Vuex 商店之间的关系。并不是我们所有的组件都连接到 Vuex,只有这三个组件是:
-
/src/views/EventCreate.vue
-
/src/views/EventDetails.vue
-
/src/views/EventList.vue
以下是这些 Vue 组件当前如何连接到 Vuex 商店的说明:
正如你所看到的,Vuex店里有三个态项目:user
,event
,和events
。并且这些组件正在消耗所有这三个。
这三个组件还向 Vuex 存储分派各种操作,这是一个说明:
我们在代码库中还有其他组件,但这三个组件是唯一链接到 Vuex 商店的组件,因此它们将是我们在本教程中的重点。
地图助手
Map 辅助函数为我们的组件连接到 Vuex 商店提供了更简单的方法。目前,我们正在获取状态并通过this.$store
. 相反this.$store.state.event
,使用地图助手将允许我们简单地编写this.event
.
Vuex 的 API 有四个地图助手。
-
mapState
-
mapGetters
-
mapActions
-
mapMutations
对于我们的应用程序,我们将使用mapState
和mapActions
。
地图状态
首先,我们将使用mapState
来简化我们访问user
和event
从 Vuex 商店访问的方式。
我们需要更改三个组件,让我们从EventCreate
.
首先,导入mapState
:
/src/views/EventCreate.vue
<script>
import { v4 as uuidv4 } from 'uuid'
import { mapState } from 'vuex' // ADD
...
然后我们将使用mapState
向组件添加计算属性user
:
/src/views/EventCreate.vue
computed: {
...mapState(['user'])},
(三点语法是一种 ES6 语法,称为 扩展运算符)
在上面的代码中,mapState
函数将返回一个包含user
方法的对象,而扩展运算符(三个点)将帮助我们将此user
方法放入computed
对象中。因此,user
成为computed
组件的属性。
现在我们可以this.user
在我们的组件中使用而不是this.$store.state.user
:
/src/views/EventCreate.vue
onSubmit() {
const event = {
...this.event,
id: uuidv4(),
organizer: this.user // CHANGE
}
...
该mapState
函数添加了一个名为user
"跟踪"user
来自我们商店的状态的计算属性。这在功能上与this.$store.state.user
直接使用相同。
对于 EventDetails 组件,我们将删除现有的计算属性event
,并使用该mapState
函数创建相同的计算属性:
/src/views/EventDetails.vue
<script>
import { mapState } from 'vuex' // ADD
...
computed: {
/* REMOVE
event() {
return this.$store.state.event
}
*/
...mapState(['event']) // ADD
}
对于EventList
组件,我们将再次执行完全相同的操作:
/src/views/EventList.vue
<script>
import EventCard from '@/components/EventCard.vue'
import { mapState } from 'vuex' // ADD
export default {
...
computed: {
/* REMOVE
event() {
return this.$store.state.event
}
*/
...mapState(['events']) // ADD
}
如果我们需要从 store 映射到多个状态项,我们可以将它添加到数组参数中:
/src/views/EventList.vue
computed: {
...mapState(['events', 'user']) // CHANGE}
随着user
添加为新的计算属性,我们可以在这样的模板,使其:
/src/views/EventList.vue
<template>
<h1>Events for {{ user }}</h1>
...
通过使用mapState
,我们现在可以像访问常规computed
属性一样访问状态。
地图操作
尽管我们改进了访问状态的方式,但组件仍然依赖于this.$store.dispatch
将操作分派到商店。而不是写作this.$store.dispatch('createEvent', event)
,我们希望能够只写this.createEvent(event)
。
所以,mapAction
接下来我们将使用助手。
从EventCreate组件开始:
/src/views/EventCreate.vue
<script>
import { mapState, mapActions } from 'vuex' // CHANGE
...
methods: {
...mapActions(['createEvent']), // ADD
onSubmit() {
const event = {
该mapActions
函数将向createEvent
组件注入一个方法,我们可以使用它来分派动作。
所以现在我们可以直接调用this.createEvent
而不是this.$store.dispatch
:
onSubmit() {
const event = {
...
}
// this.$store.dispatch('createEvent', event) REMOVE
this.createEvent(event) // ADD
.then(() => {
...
让我们对其他两个组件应用相同的更改。
/src/views/EventDetails.vue
<script>
import { mapState, mapActions } from 'vuex' // CHANGE
...
export default {
props: ['id'],
created() {
// this.$store.dispatch('fetchEvent', this.id) REMOVE
this.fetchEvent(this.id) // CHANGE
.catch(error => {
...
})
},
computed: {
...mapState(['event'])
},
// ADD
methods: {
...mapActions(['fetchEvent'])
}
}
/src/views/EventList.vue
import { mapState, mapActions } from 'vuex' // CHANGE
...
export default {
created() {
this.fetchEvents() // CHANGE
.catch(error => {
...
})
},
computed: {
...mapState(['events', 'user'])
},
// ADD
methods: {
...mapActions(['fetchEvents'])
}
}
通过使用mapState
and mapActions
,我们的组件现在更干净了。
现在,让我们将注意力转移到 Vuex 代码上。
模块
目前,这个应用程序中的所有 Vuex 代码都位于同一个文件中,这对于一个简单的应用程序来说很好。但是随着我们的应用程序的增长,代码注定会变得庞大而复杂,因此这种单一文件设置并不是管理我们商店的最佳方式。作为解决方案,我们可以将其分解为具有称为modules的功能的有组织的夹头。
我们当前的 Vuex 代码可以重构为两个独立的独立模块:user
和event
. 该user
模块将包含与user
状态相关的所有 Vuex 代码,该event
模块将包含与event
和相关的所有 Vuex 代码events
。
我们将user
首先提取模块的代码。然后,我们将转到event
模块。
让我们在/src/store 中创建一个新的模块文件夹,我们将在其中放置所有 Vuex 模块。
用户模块
现在,让我们从user
模块开始。
在模块文件夹中创建一个名为user.js的文件。该文件将保存所有与用户相关的 Vuex 代码。我们 Vuex 商店中唯一与用户相关的代码就是状态。因此,让我们将状态提取到新文件中:user``user
/src/store/modules/user.js
export default {
state: {
user: 'Adam Jahr'
}
}
Vuex 模块与主 Vuex 存储具有相似的结构。例如,我们state
这里有一个属性,就像store/index.js 中的代码一样。但是由于没有任何与 相关的突变或操作user
,我们没有在这个模块中使用actions
和mutations
属性。
为了让它看起来更像一个现实生活中的模块,让我们将用户状态扩展为一个包含更多用户相关信息的对象:
/src/store/modules/user.js
export default {
state: {
user: {
id: 'abc123', // ADD
name: 'Adam Jahr'
}
}
}
现在我们有一个名为 的模块user
,它也是它包含的状态的名称。为了避免混淆,让我们将user
状态重命名为userInfo
.
/src/store/modules/user.js
export default {
state: {
userInfo: { // RENAME
id: 'abc123',
name: 'Adam Jahr'
}
}
}
回到store/index.js,我们必须导入user
模块并将其"插入"带有modules
属性的商店:
/src/store/modules/user.js
import { createStore } from 'vuex'
import EventService from '@/services/EventService.js'
import user from './modules/user.js' // ADD
export default createStore({
state: {
// user: 'Adam Jahr', REMOVE
events: [],
event: {}
},
mutations: {
...
},
actions: {
...
},
modules: { user } // CHANGE
})
由于现在用户数据存在于模块中,我们必须相应地修改我们的组件代码。具体来说,我们需要在状态前加上正确的模块名称。
在EventList
组件中,更改user
为user.userInfo.name
:
/src/views/EventList.vue
<template>
<h1>Events for {{ user.userInfo.name }}</h1>
<div class="events">
...
在上面的代码中,user
指的是模块,而不是状态。由于我们已经重命名了它,userInfo
现在是状态,并且name
是状态对象内的一个属性。
我们不必更改映射到 store 的方式,mapState
因为模块名称是user
,这与我们已经使用的名称相同mapState
。
最后在EventCreate
组件中,我们需要更改this.user
为this.user.userInfo.name
:
/src/views/EventCreate.vue
const event = {
...this.event,
id: uuidv4(),
organizer: this.user.userInfo.name // CHANGE
}
而这与它的user
模块。接下来,我们将处理event
模块。
事件模块
首先,event.js
在store/modules文件夹中创建一个文件。然后将所有与事件相关的state
,mutations
以及actions
在新的文件。
/src/store/modules/event.js
import EventService from '@/services/EventService.js'
export default {
state: {
events: [],
event: {}
},
mutations: {
ADD_EVENT(state, event) {
state.events.push(event)
},
SET_EVENT(state, event) {
state.event = event
},
SET_EVENTS(state, events) {
state.events = events
}
},
actions: {
createEvent({ commit }, event) {
return EventService.postEvent(event)
.then(() => {
commit('ADD_EVENT', event)
})
.catch(error => {
throw(error)
})
},
fetchEvents({ commit }) {
return EventService.getEvents()
.then(response => {
commit('SET_EVENTS', response.data)
})
.catch(error => {
throw(error)
})
},
fetchEvent({ commit, state }, id) {
const existingEvent = state.events.find(event => event.id === id)
if (existingEvent) {
commit('SET_EVENT', existingEvent)
} else {
return EventService.getEvent(id)
.then(response => {
commit('SET_EVENT', response.data)
})
.catch(error => {
throw(error)
})
}
}
}
}
(这基本上是来自store/index.js的整个对象字面量,不包括modules
属性)
我们还将事件状态重命名为currentEvent
,使其与事件模块的名称不同:
/src/store/modules/event.js
import EventService from '@/services/EventService.js'
export default {
state: {
events: [],
currentEvent: {} // RENAME
},
mutations: {
ADD_EVENT(state, event) {
state.events.push(event)
},
SET_EVENT(state, event) {
state.currentEvent = event // RENAME
},
...
现在,让我们回到store/index.js。就像我们对user
模块所做的一样,我们必须导入event
模块并将其"插入"商店:
/src/store.index.js
import { createStore } from 'vuex'
// import EventService from '@/services/EventService.js' REMOVE
import user from './modules/user.js'
import event from './modules/event.js' // ADD
export default createStore({
modules: {
user,
event // ADD
}
})
随着 Vuex 代码中的这些更改,我们的组件也必须更新以使用该event
模块。
由于现在事件位于event
模块内,因此我们必须使用名称event
而不是events
使用mapState
.
/src/views/EventList.vue
computed: {
...mapState(['user', 'event'])
(这基本上只是删除s
from events
)
然后我们将events
在模板中使用模块名称作为前缀,event
。
/src/views/EventList.vue
<template>
...
<div class="events">
<EventCard v-for="event in event.events" :key="event.id" :event="event" />
...
所以再一次,event
in[event.events](http://event.events)
是模块名称,并且events
是event
模块内部的状态项之一。
我们将在EventDetails组件中进行类似的更改:
/src/views/EventDetails.vue
<template>
<div v-if="event.currentEvent">
<h1>{{ event.currentEvent.title }}</h1>
<p>
{{ event.currentEvent.time }} on {{ event.currentEvent.date }} @
{{ event.currentEvent.location }}
</p>
<p>{{ event.currentEvent.description }}</p>
</div>
</template>
无需更改我们mapState
在此组件中的映射方式,因为我们已经映射到event
.
现在应用程序应该像以前一样工作,但event
数据和user
数据位于两个独立的模块中。
命名空间
你可能已经注意到,我们没有改变我们分发动作的方式,代码仍然有效。
当一个动作被分派时,具有相同动作的模块将有机会处理它,所以我们不必指定我们要分派到哪个模块。Vuex 是这样设计的,因此多个模块可以潜在地处理相同的动作名称。
但是如果我们确实想明确我们要分派到哪个特定模块,我们可以对 Vuex 模块使用命名空间。
在模块中,添加一个namespaced
属性并将其设置为true
.
/src/store/modules/event.js
export default {
namespaced: true,
state: {
events: [],
currentEvent: {
...
/src/store/modules/user.js
export default {
namespaced: true,
state: {
userInfo: {
...
回到组件中,将正确的模块名称添加到mapActions
:
/src/views/EventCreate.vue
methods: {
...mapActions('event', ['createEvent']),
onSubmit() {
/src/views/EventDetails.vue
methods: {
...mapActions('event', ['fetchEvent'])
}
/src/views/EventList.vue
methods: {
...mapActions('event', ['fetchEvents'])
}
现在,我们所有分派的动作都被正确地发送到正确的模块。
结论
我们已经了解了 Vuex 中两个最有用的重构工具,地图助手和模块。随着您的应用程序变得越来越大,这些工具对于保持代码整洁至关重要。