阐述 Vuex 源码中 `getters` 的实现,包括其依赖收集和缓存机制。

各位观众老爷,大家好!今天咱们来聊聊 Vuex 里面的 getters,这玩意儿看起来简单,但背后的实现却藏着不少小秘密。咱们要像剥洋葱一样,一层一层地扒开它,看看它到底是怎么工作的。

一、getters 是个啥?为什么要用它?

首先,咱们得明确 getters 的作用。简单来说,getters 就像 Vuex store 的计算属性。它允许我们从 state 中派生出一些新的数据,而且这些数据是响应式的。

想想一下,咱们有一个 state 里面存着一个用户列表:

const state = {
  users: [
    { id: 1, name: '张三', age: 20 },
    { id: 2, name: '李四', age: 30 },
    { id: 3, name: '王五', age: 25 }
  ]
};

现在,咱们想要获取所有年龄大于 25 岁的用户。如果没有 getters,咱们可能需要在组件里写一个计算属性:

<template>
  <div>
    <ul>
      <li v-for="user in olderUsers" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  computed: {
    olderUsers() {
      return this.$store.state.users.filter(user => user.age > 25);
    }
  }
};
</script>

这样写当然没问题,但是如果很多组件都需要用到这个 olderUsers,那咱们岂不是要在每个组件里都写一遍?这代码就重复了嘛!而且,万一哪天需求变了,判断条件不是大于 25 岁,而是大于 30 岁了,那咱们岂不是要改很多地方?

这时候,getters 就派上用场了。咱们可以把这个逻辑放到 getters 里面:

const getters = {
  olderUsers: (state) => {
    return state.users.filter(user => user.age > 25);
  }
};

然后在组件里就可以这样用了:

<template>
  <div>
    <ul>
      <li v-for="user in olderUsers" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  computed: {
    olderUsers() {
      return this.$store.getters.olderUsers;
    }
  }
};
</script>

这样一来,代码就简洁多了,而且也更容易维护。以后如果需求变了,只需要改 getters 里面的逻辑就可以了。

二、getters 的实现:核心代码剖析

好了,了解了 getters 的作用,咱们接下来就来看看它的实现。Vuex 的 getters 实现主要涉及到两个方面:依赖收集和缓存。

  1. 依赖收集

getters 能够响应式地更新,是因为它能够追踪它所依赖的 state。当 state 发生变化时,getters 会自动重新计算。这个追踪的过程就是依赖收集。

在 Vuex 的源码中,依赖收集是通过 Vue 的响应式系统来实现的。具体来说,当咱们访问一个 getter 时,Vuex 会创建一个 Watcher 实例。这个 Watcher 实例会记录下 getter 依赖的所有 state。当这些 state 发生变化时,Watcher 就会收到通知,然后重新计算 getter 的值。

咱们来看一下 Vuex 中 getters 的初始化代码(简化版):

function installModule(store, rootState, path, module, hot) {
  // ...

  // register getters
  if (module.getters) {
    forEachValue(module.getters, (getter, key) => {
      const namespacedType = namespace + key;
      // 定义 getter
      Object.defineProperty(store.getters, namespacedType, {
        get: () => {
          return module.getters[key](
            store.state,
            store.getters,
            rootState,
            rootGetters
          );
        },
        enumerable: true // for local getters
      });
    });
  }

  // ...
}

这段代码的关键在于 Object.defineProperty 里面的 get 函数。当咱们访问 store.getters.olderUsers 时,这个 get 函数就会被调用。这个 get 函数会执行 module.getters[key],也就是咱们定义的 olderUsers 函数。

olderUsers 函数执行的过程中,如果它访问了 state.users,那么 Vue 的响应式系统就会记录下这个依赖关系。当 state.users 发生变化时,Vue 就会通知 Watcher,然后 Watcher 就会重新计算 olderUsers 的值。

用一个表格来总结一下依赖收集的过程:

步骤 描述 涉及代码
1 访问 store.getters.olderUsers Object.definePropertyget 函数被调用
2 olderUsers 函数执行,访问 state.users module.getters[key](store.state, store.getters, rootState, rootGetters)
3 Vue 的响应式系统记录下 olderUsers 依赖 state.users Vue 的 ObserverDep 相关逻辑
4 state.users 发生变化 Vue 的 Observer 通知 Dep
5 Dep 通知 Watcher Dep.notify()
6 Watcher 重新计算 olderUsers 的值 Watcher.update() -> 执行 olderUsers 函数
  1. 缓存机制

getters 还有一个很重要的特性就是缓存。也就是说,如果 getter 依赖的 state 没有发生变化,那么 getter 的值就不会重新计算,而是直接从缓存中读取。

这个缓存机制可以提高性能,避免不必要的计算。

在 Vuex 的源码中,缓存是通过 Watcher 实例来实现的。Watcher 实例会保存 getter 的上一次计算结果。当 getter 依赖的 state 没有发生变化时,Watcher 就会直接返回上次计算的结果,而不会重新执行 getter 函数。

咱们来看一下 Vuex 中 Watcher 的相关代码(简化版):

class Watcher {
  constructor(vm, expOrFn, cb, options, isRenderWatcher) {
    this.vm = vm;
    this.getter = expOrFn; // getter 函数
    this.cb = cb;
    this.value = this.get(); // 初始值
  }

  get() {
    pushTarget(this); // 将当前 Watcher 放入 Dep.target
    let value;
    try {
      value = this.getter.call(this.vm, this.vm); // 执行 getter 函数
    } catch (e) {
      // ...
    } finally {
      popTarget(); // 移除 Dep.target
    }
    return value;
  }

  update() {
    // ...
    const newValue = this.get(); // 重新计算 getter 的值
    const oldValue = this.value;
    this.value = newValue;
    this.cb.call(this.vm, newValue, oldValue); // 执行回调函数
    // ...
  }
}

Watcherget 函数中,会先将当前 Watcher 实例放入 Dep.target 中。这样,在执行 getter 函数的过程中,如果访问了 state,那么 Vue 的响应式系统就会将当前 Watcher 实例添加到 state 对应的 Dep 中。

Watcherupdate 函数中,会重新计算 getter 的值,并将新值和旧值进行比较。如果新值和旧值不一样,那么就会执行回调函数。

关键在于,如果 getter 依赖的 state 没有发生变化,那么 update 函数就不会被调用,getter 的值也不会重新计算。

再用一个表格来总结一下缓存机制:

步骤 描述 涉及代码
1 访问 store.getters.olderUsers Object.definePropertyget 函数被调用
2 如果 olderUsers 依赖的 state 没有发生变化 Watcher 实例会直接返回上次计算的结果,不会重新执行 olderUsers 函数
3 如果 olderUsers 依赖的 state 发生了变化 Watcher 实例会重新计算 olderUsers 的值,并将新值和旧值进行比较,如果不一样,则执行回调函数

三、getters 的高级用法:传递参数

除了基本的用法之外,getters 还可以传递参数。这在某些场景下非常有用。

例如,咱们想要根据用户的 id 获取用户信息。可以这样定义 getters

const getters = {
  getUserById: (state) => (id) => {
    return state.users.find(user => user.id === id);
  }
};

然后在组件里就可以这样用了:

<template>
  <div>
    <p>User Name: {{ getUserName(1) }}</p>
  </div>
</template>

<script>
export default {
  computed: {
    getUserName() {
      return (id) => this.$store.getters.getUserById(id).name;
    }
  }
};
</script>

需要注意的是,传递参数的 getters 不会被缓存。也就是说,每次调用 getUserById 都会重新计算。

四、getters 的注意事项

  • getters 应该只包含纯函数。也就是说,getters 不应该有任何副作用,也不应该修改 state
  • 传递参数的 getters 不会被缓存。
  • 避免在 getters 中进行复杂的计算,以免影响性能。

五、getters 的应用场景

  • state 中派生出新的数据。
  • state 进行过滤、排序、转换等操作。
  • 封装复杂的业务逻辑。

六、总结

getters 是 Vuex 中一个非常重要的概念。它允许咱们从 state 中派生出新的数据,并且这些数据是响应式的。getters 的实现主要涉及到依赖收集和缓存。通过理解 getters 的实现原理,咱们可以更好地使用 Vuex,编写出更高效、更易维护的代码。

总而言之,getters 就像是 Vuex 仓库里的“变形金刚”,它能根据 state 的变化,灵活地“变形”出各种咱们需要的数据。 掌握好 getters,就能让咱们的 Vuex 应用更加强大!

好了,今天的讲座就到这里。 希望大家有所收获! 感谢各位的收听!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注