各位观众老爷,大家好!今天咱们来聊聊 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 实现主要涉及到两个方面:依赖收集和缓存。
- 依赖收集
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.defineProperty 的 get 函数被调用 |
| 2 | olderUsers 函数执行,访问 state.users |
module.getters[key](store.state, store.getters, rootState, rootGetters) |
| 3 | Vue 的响应式系统记录下 olderUsers 依赖 state.users |
Vue 的 Observer 和 Dep 相关逻辑 |
| 4 | state.users 发生变化 |
Vue 的 Observer 通知 Dep |
| 5 | Dep 通知 Watcher |
Dep.notify() |
| 6 | Watcher 重新计算 olderUsers 的值 |
Watcher.update() -> 执行 olderUsers 函数 |
- 缓存机制
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); // 执行回调函数
// ...
}
}
在 Watcher 的 get 函数中,会先将当前 Watcher 实例放入 Dep.target 中。这样,在执行 getter 函数的过程中,如果访问了 state,那么 Vue 的响应式系统就会将当前 Watcher 实例添加到 state 对应的 Dep 中。
在 Watcher 的 update 函数中,会重新计算 getter 的值,并将新值和旧值进行比较。如果新值和旧值不一样,那么就会执行回调函数。
关键在于,如果 getter 依赖的 state 没有发生变化,那么 update 函数就不会被调用,getter 的值也不会重新计算。
再用一个表格来总结一下缓存机制:
| 步骤 | 描述 | 涉及代码 |
|---|---|---|
| 1 | 访问 store.getters.olderUsers |
Object.defineProperty 的 get 函数被调用 |
| 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 应用更加强大!
好了,今天的讲座就到这里。 希望大家有所收获! 感谢各位的收听!