各位观众老爷,大家好!今天咱们来聊聊 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 应用更加强大!
好了,今天的讲座就到这里。 希望大家有所收获! 感谢各位的收听!