51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

使用Map Helpers 和 Modules重构Vuex

500.jpg

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,您可以尝试使用示例应用程序。

该应用程序包含三个页面:

1.1618933756226.jpg

  • Events:显示事件列表的页面(如简单的 Meetup.com)

  • 关于:显示一些描述应用程序的文本的页面

  • 创建事件:带有用于创建新事件的表单的页面

现在,让我们来看看代码。我们的主要组件托管在src/views文件夹中。

本教程的重点是 Vue 组件和 Vuex 商店之间的关系。并不是我们所有的组件都连接到 Vuex,只有这三个组件是:

  • /src/views/EventCreate.vue

  • /src/views/EventDetails.vue

  • /src/views/EventList.vue

以下是这些 Vue 组件当前如何连接到 Vuex 商店的说明:

QQ截图20210902113850.jpg

正如你所看到的,Vuex店里有三个态项目:userevent,和events。并且这些组件正在消耗所有这三个。

这三个组件还向 Vuex 存储分派各种操作,这是一个说明:

QQ截图20210902113917.jpg


我们在代码库中还有其他组件,但这三个组件是唯一链接到 Vuex 商店的组件,因此它们将是我们在本教程中的重点。

地图助手

Map 辅助函数为我们的组件连接到 Vuex 商店提供了更简单的方法。目前,我们正在获取状态并通过this.$store. 相反this.$store.state.event,使用地图助手将允许我们简单地编写this.event.

Vuex 的 API 有四个地图助手。

  • mapState

  • mapGetters

  • mapActions

  • mapMutations

对于我们的应用程序,我们将使用mapStatemapActions

地图状态

首先,我们将使用mapState来简化我们访问userevent从 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'])
  }
}

通过使用mapStateand mapActions,我们的组件现在更干净了。

现在,让我们将注意力转移到 Vuex 代码上。

模块

目前,这个应用程序中的所有 Vuex 代码都位于同一个文件中,这对于一个简单的应用程序来说很好。但是随着我们的应用程序的增长,代码注定会变得庞大而复杂,因此这种单一文件设置并不是管理我们商店的最佳方式。作为解决方案,我们可以将其分解为具有称为modules的功能的有组织的夹头。

我们当前的 Vuex 代码可以重构为两个独立的独立模块:userevent. 该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,我们没有在这个模块中使用actionsmutations属性。

为了让它看起来更像一个现实生活中的模块,让我们将用户状态扩展为一个包含更多用户相关信息的对象:

/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组件中,更改useruser.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.userthis.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文件夹中创建一个文件。然后将所有与事件相关的statemutations以及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'])

(这基本上只是删除sfrom events

然后我们将events在模板中使用模块名称作为前缀,event

/src/views/EventList.vue

<template>
  ...
  <div class="events">
    <EventCard v-for="event in event.events" :key="event.id" :event="event" />
  ...

所以再一次,eventin[event.events](http://event.events)是模块名称,并且eventsevent模块内部的状态项之一。

我们将在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 中两个最有用的重构工具,地图助手和模块。随着您的应用程序变得越来越大,这些工具对于保持代码整洁至关重要。

赞(0)
未经允许不得转载:工具盒子 » 使用Map Helpers 和 Modules重构Vuex