深入探索Vue Getters和mapGetters的原理及使用详解

2024-07-01 16:54:27 浏览数 (1)

在Vue.js的状态管理中,Vuex是一个非常重要的工具,它帮助开发者集中管理应用的状态。Vuex的核心概念包括state、mutations、actions、getters和modules。今天,我们要深入探讨其中一个关键部分:getters,以及它的相关辅助函数mapGetters。通过详细介绍getters的原理和实现过程,希望能帮助你更好地理解和使用它们。

什么是Vue Getters?

Vuex中的getters可以被视为store的计算属性。就像Vue组件中的计算属性一样,getters的返回值会基于其依赖被缓存起来,且只有当它的依赖值发生变化时才会重新计算。这使得getters非常适合用于从store中的state派生出一些状态。

基本使用

首先,让我们看一个简单的例子:

代码语言:javascript复制
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: 'Learn Vue', done: true },
      { id: 2, text: 'Learn Vuex', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    },
    doneTodosCount: (state, getters) => {
      return getters.doneTodos.length
    }
  }
})

在上面的代码中,我们定义了一个doneTodos的getter,它会返回所有已完成的任务。同时,我们还定义了一个doneTodosCount的getter,它依赖于doneTodos,返回已完成任务的数量。

访问Getters

你可以通过store.getters来访问getters:

代码语言:javascript复制
store.getters.doneTodos // -> [{ id: 1, text: 'Learn Vue', done: true }]
store.getters.doneTodosCount // -> 1

在组件中使用Getters

在Vue组件中,你可以使用this.$store.getters来访问getters:

代码语言:javascript复制
computed: {
  doneTodos () {
    return this.$store.getters.doneTodos
  }
}

这虽然工作正常,但对于多个getters的访问会显得有些冗长。为了解决这个问题,我们可以使用mapGetters辅助函数。

使用mapGetters

mapGetters是一个辅助函数,它可以帮助我们将store中的getter映射到局部计算属性。它可以极大地简化在组件中使用getters的代码量。

基本使用

首先,我们需要在组件中导入mapGetters

代码语言:javascript复制
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'doneTodos',
      'doneTodosCount'
    ])
  }
}

现在,我们可以直接在模板中使用这些计算属性:

代码语言:html复制
<template>
  <div>
    <p>Done Todos: {{ doneTodos }}</p>
    <p>Done Todos Count: {{ doneTodosCount }}</p>
  </div>
</template>

别名

有时候,我们可能想要为映射的计算属性指定别名。这时可以使用对象形式的参数:

代码语言:javascript复制
computed: {
  ...mapGetters({
    completedTasks: 'doneTodos',
    completedTasksCount: 'doneTodosCount'
  })
}

这样,我们就可以在模板中使用别名:

代码语言:html复制
<template>
  <div>
    <p>Completed Tasks: {{ completedTasks }}</p>
    <p>Completed Tasks Count: {{ completedTasksCount }}</p>
  </div>
</template>

Getters的原理和实现

为了更深入地理解getters的工作原理,我们需要了解Vuex的内部实现。Vuex是基于Vue的响应系统构建的,因此getters的实现与Vue的计算属性有很多相似之处。

创建Getters

当我们创建一个store时,Vuex会遍历我们定义的所有getters,并为每一个getter创建一个计算属性。这些计算属性的结果会被缓存,只有当它们的依赖(即state或者其他getters)发生变化时才会重新计算。

代码语言:javascript复制
class Store {
  constructor (options = {}) {
    // ...
    const store = this
    const { getters } = options

    this.getters = {}
    Object.keys(getters).forEach(key => {
      const fn = getters[key]
      Object.defineProperty(store.getters, key, {
        get: () => fn(store.state, store.getters)
      })
    })
    // ...
  }
}

在上面的代码中,我们可以看到Vuex通过Object.defineProperty为每一个getter定义了一个属性,这个属性的getter函数会返回计算后的结果。

响应式系统

Vuex的state是响应式的,这意味着当我们改变state中的数据时,所有依赖于这些数据的getters都会自动更新。Vuex通过Vue的Vue.observable方法将state变成响应式对象。

代码语言:javascript复制
const state = Vue.observable({
  todos: [
    { id: 1, text: 'Learn Vue', done: true },
    { id: 2, text: 'Learn Vuex', done: false }
  ]
})

这样,当我们改变state中的todos时,所有依赖于todos的getters(例如doneTodosdoneTodosCount)都会自动重新计算,并触发相关的视图更新。

深入理解mapGetters

mapGetters是Vuex提供的一个非常有用的辅助函数,它的实现也相对简单。mapGetters的主要作用是将store中的getters映射到组件的计算属性。

mapGetters的实现

我们来看看mapGetters的实现:

代码语言:javascript复制
export function mapGetters (getters) {
  const res = {}
  normalizeMap(getters).forEach(({ key, val }) => {
    res[key] = function mappedGetter () {
      return this.$store.getters[val]
    }
  })
  return res
}

在上面的代码中,mapGetters首先通过normalizeMap函数将传入的参数规范化为一个数组,然后遍历这个数组,为每一个getter创建一个计算属性。这些计算属性的getter函数会返回this.$store.getters中的对应值。

使用normalizeMap

normalizeMap函数的作用是将传入的参数(可以是数组或对象)规范化为一个标准的对象数组:

代码语言:javascript复制
function normalizeMap (map) {
  if (!isValidMap(map)) {
    return []
  }
  return Array.isArray(map)
    ? map.map(key => ({ key, val: key }))
    : Object.keys(map).map(key => ({ key, val: map[key] }))
}

如果传入的是一个数组,normalizeMap会将每一个数组元素转化为一个对象,键和值相同;如果传入的是一个对象,normalizeMap会将每一个键值对转化为一个对象,键和值分别对应原对象的键和值。

Getters和mapGetters的实际应用

在实际项目中,getters和mapGetters可以帮助我们更好地组织和管理应用状态。让我们通过一个稍微复杂的例子来进一步理解它们的实际应用。

例子:Todo应用

假设我们在开发一个Todo应用,这个应用需要展示所有任务、已完成任务、未完成任务以及任务的数量。我们可以通过getters来实现这些功能。

首先,我们定义store的state和getters:

代码语言:javascript复制
const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: 'Learn Vue', done: true },
      { id: 2, text: 'Learn Vuex', done: false },
      { id: 3, text: 'Build something awesome', done: false }
    ]
  },
  getters: {
    allTodos: state => state.todos,
    doneTodos: state => state.todos.filter(todo => todo.done),
    undoneTodos: state => state.todos.filter(todo => !todo.done),
    totalTodosCount: state => state.todos.length,
    doneTodosCount: (state, getters) => getters.doneTodos.length,
    undoneTodosCount: (state, getters) => getters.undoneTodos.length
  }
})

然后,在组件中使用这些getters:

代码语言:javascript复制
import { mapGetters } from 'vuex'

export default {
  computed: {
    ...mapGetters([
      'allTodos',
      'doneTodos',
      'undoneTodos',
      'totalTodosCount',
      'doneTodosCount',
      'undoneTodosCount'
    ])
  }
}

在模板中展示任务和统计信息:

代码语言:html复制
<template>
  <div>
    <h1>Todo List</h1>
    <p>Total Todos: {{ totalTodosCount }}</p>
    <p>Done Todos: {{ doneTodosCount }}</p>
    <p>Undone Todos: {{ undoneTodosCount }}</p>

    <h2>All Todos</h2>
    <ul>
      <li v-for="todo in allTodos" :key="todo.id">{{ todo.text }}</li>
    </ul>

    <h2>Done Todos</h2>
    <ul>
      <li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li>
    </ul>

    <h2>Undone Todos</h2>
    <ul>
      <li v-for="todo in undoneTodos" :key="todo.id">{{ todo.text }}</li>
    </ul>
  </div>
</template>

通过这种方式,我们可以清晰地展示所有任务、已完成任务和未完成任务,以及相关的统计信息。而且,这些数据都是通过getters从state派生出来的,当state中的任务列表发生变化时,视图会自动更新。

优化和最佳实践

在实际开发中,除了正确使用getters和mapGetters,我们还可以采取一些优化和最佳实践来提升代码的可维护性和性能。

避免不必要的计算

虽然getters的结果会被缓存,但在设计getters时仍然要注意避免不必要的计算。例如,如果一个getter依赖于另一个getter,我们应该尽量减少重复计算。

模块化

对于大型应用,我们可以将store拆分成多个模块,每个模块都有自己的state、mutations、actions和getters。这样可以使代码更清晰,更易于管理。

代码语言:javascript复制
const moduleA = {
  state: () => ({
    todos: []
  }),
  getters: {
    doneTodos: state => state.todos.filter(todo => todo.done)
  },
  mutations: {
    // ...
  },
  actions: {
    // ...
  }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA
  }
})

在组件中使用模块的getters:

代码语言:javascript复制
computed: {
  ...mapGetters('a', [
    'doneTodos'
  ])
}

异步操作

虽然getters不应该包含异步操作,但我们可以在actions中进行异步操作,然后通过mutations更新state,从而触发getters的重新计算。

代码语言:javascript复制
const store = new Vuex.Store({
  state: {
    todos: []
  },
  getters: {
    doneTodos: state => state.todos.filter(todo => todo.done)
  },
  mutations: {
    setTodos (state, todos) {
      state.todos = todos
    }
  },
  actions: {
    fetchTodos ({ commit }) {
      // 假设我们有一个API调用来获取todos
      fetchTodosFromAPI().then(todos => {
        commit('setTodos', todos)
      })
    }
  }
})

性能优化

在高性能需求的应用中,我们可以利用Vuex的插件系统来优化getters的性能。例如,我们可以编写一个插件来对getters的结果进行缓存,从而避免频繁的计算。

代码语言:javascript复制
function createGettersCachePlugin () {
  return store => {
    const cache = {}

    store.subscribe((mutation, state) => {
      // 在每次mutation后清除缓存
      Object.keys(cache).forEach(key => delete cache[key])
    })

    store._wrappedGetters = Object.keys(store._wrappedGetters).reduce((wrappedGetters, key) => {
      const getter = store._wrappedGetters[key]
      wrappedGetters[key] = (state, getters) => {
        if (!cache[key]) {
          cache[key] = getter(state, getters)
        }
        return cache[key]
      }
      return wrappedGetters
    }, {})
  }
}

const store = new Vuex.Store({
  // ...
  plugins: [createGettersCachePlugin()]
})

这个插件在每次mutation后清除缓存,并对getters的结果进行缓存,从而减少不必要的计算。

总结

Vuex的getters和mapGetters是非常强大的工具,它们可以帮助我们从store中的state派生出新的状态,并在组件中方便地使用这些状态。在实际开发中,我们可以通过合理使用getters和mapGetters,提高代码的可维护性和性能。同时,我们还可以采用一些优化和最佳实践,使我们的应用更加健壮和高效。

希望通过本文的详细介绍,你能够对Vuex的getters和mapGetters有更深入的理解,并在实际项目中更好地应用它们。祝你在Vue.js的世界中编程愉快!


我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞