各位观众老爷,晚上好!今儿个咱们不聊八卦,聊点硬核的,抠一抠 Vuex 源码里 getters
这玩意儿的实现,看看它咋做依赖收集,又咋玩转缓存的。准备好了吗?老司机要发车了!
开场白:Getters,这磨人的小妖精
getters
在 Vuex 里扮演着什么角色?简单说,它就是个计算属性的集中营。当你需要从 state
派生出一些数据,并且这些数据可能被多个组件用到时,getters
就派上用场了。
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 10
},
getters: {
doubleCount: (state) => state.count * 2,
// 还能接受其他 getters 作为参数
tripleCount: (state, getters) => getters.doubleCount + state.count
},
mutations: {
increment (state) {
state.count++
}
}
})
export default store
瞧见没?doubleCount
和 tripleCount
都是 getters
。 doubleCount
直接从 state
拿数据算, tripleCount
还能用上其他的 getters
。 这就像厨房里的食材和菜谱,state
是食材,getters
是菜谱,你拿着食材,照着菜谱就能做出美味佳肴。
源码探秘:Getters 是怎么炼成的?
想知道 getters
的实现,得扒一扒 Vuex 的源码。 别怕,我带着你,保证不迷路。
-
Store 的初始化:Getter 的登记
在
Store
实例化的时候,Vuex 会把我们定义的getters
登记在册。 具体来说,是在Store
的构造函数里调用resetStoreVM
方法,这个方法里会创建Vue实例,并将 getters 转换为计算属性。// src/store.js constructor (options = {}) { // ... this.strict = options.strict this._committing = false this._actions = Object.create(null) this._actionSubscribers = [] this._mutations = Object.create(null) this._wrappedGetters = Object.create(null) this._modules = new ModuleCollection(options) this._modulesNamespaceMap = Object.create(null) this._subscribers = [] this._watcherVM = new Vue() this._makeLocalGettersCache = Object.create(null) const state = this._modules.root.state installModule(this, state, [], this._modules.root) // initialize the store vm, which is the root of the whole vuex // instance. resetStoreVM(this, state) // ... }
resetStoreVM
函数负责创建 Vue 实例,并将getters
转换为计算属性。// src/store.js function resetStoreVM (store, state, hot) { const oldVm = store._vm // bind store public getters store.getters = {} const wrappedGetters = store._wrappedGetters const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for devtools }) }) // use a Vue instance to store the state tree // suppress warnings just in case the user has added // some funky global mixins // use silent mode since we expose these vm.s internally as // private properties. const silent = Vue.config.silent Vue.config.silent = true store._vm = new Vue({ data: { $$state: state }, computed }) Vue.config.silent = silent // ... }
这段代码做了几件事:
- 遍历
wrappedGetters
,这是经过包装的getters
函数,目的是为了传入store
实例作为参数。 - 将每个
getter
函数转换为一个计算属性,放进computed
对象里。 - 给
store.getters
定义属性,这些属性的get
方法实际上是从store._vm
上读取对应的值。store._vm
就是一个 Vue 实例,它的computed
属性里包含了我们定义的getters
。
- 遍历
-
Getter 的包装:注入 Store 实例
wrappedGetters
是怎么来的?它是在installModule
函数里生成的。这个函数负责安装模块,包括根模块和子模块。// src/module/module-collection.js export function installModule (store, rootState, path, module, hot) { const isRoot = !path.length const namespace = store._modules.getNamespace(path) // register in namespace map if (module.namespaced) { if (store._modulesNamespaceMap[namespace] && __DEV__) { console.error(`[vuex] duplicate namespace ${namespace} found for module [${path.join('/')}]`) } store._modulesNamespaceMap[namespace] = module } // set state if (!isRoot && !hot) { const parentState = getNestedState(rootState, path.slice(0, -1)) const moduleName = path[path.length - 1] store._withCommit(() => { Vue.set(parentState, moduleName, module.state) }) } const local = module.context = makeLocalContext(store, namespace, path) const getters = module.getters if (getters) { forEachValue(getters, (getter, key) => { if (__DEV__ && store._wrappedGetters[namespace + key]) { console.error(`[vuex] duplicate getter key ${namespace + key} found in namespace ${namespace}.`) } store._wrappedGetters[namespace + key] = function (store) { return getter( local.state, // local state local.getters, // local getters store.rootState, // root state store.rootGetters // root getters ) } }) } // ... }
关键代码:
store._wrappedGetters[namespace + key] = function (store) { return getter( local.state, // local state local.getters, // local getters store.rootState, // root state store.rootGetters // root getters ) }
这行代码把原始的
getter
函数包装了一下,目的是为了在执行getter
函数的时候,能够传入正确的参数:local.state
: 当前模块的state
。local.getters
: 当前模块的getters
。store.rootState
: 根模块的state
。store.rootGetters
: 根模块的getters
。
这样,即使是在模块化的 Vuex 应用里,
getters
也能正确访问到所需的数据。
依赖收集:Getter 的监听之道
getters
的一大特点就是会自动进行依赖收集。啥意思?就是说,当 getter
函数依赖的 state
发生变化时,Vue 会自动重新计算 getter
的值。
这背后的功臣就是 Vue 的响应式系统。 当 getter
函数被访问时(也就是我们通过 store.getters.doubleCount
访问 getter
的值时),Vue 会记录下这个 getter
函数依赖了哪些 state
。 一旦这些 state
发生变化,Vue 就会通知这个 getter
函数重新计算。
还记得 resetStoreVM
函数里把 getters
转换成了计算属性吗?计算属性本身就具有依赖收集的功能。当计算属性的值被访问时,Vue 会自动追踪它所依赖的响应式数据。
缓存机制:Getter 的懒加载策略
getters
不仅仅会进行依赖收集,还具有缓存机制。 也就是说,只有当 getter
依赖的 state
发生变化时,才会重新计算 getter
的值。 否则,直接返回缓存的值。
这又是计算属性的功劳。 计算属性具有懒加载特性,只有当它被访问时才会计算,并且会缓存计算结果。 下次访问时,如果依赖没有发生变化,直接返回缓存值,避免重复计算。
总结:Getters 的运作流程
咱们来梳理一下 getters
的运作流程:
- 定义: 在 Vuex 的
store
中定义getters
。 - 包装: Vuex 会对
getters
进行包装,注入store
实例以及模块相关的state
和getters
。 - 登记: Vuex 会将包装后的
getters
转换为计算属性,注册到 Vue 实例上。 - 访问: 当我们访问
store.getters.xxx
时,实际上是在访问 Vue 实例上的计算属性。 - 依赖收集: Vue 的响应式系统会自动追踪
getter
函数所依赖的state
。 - 缓存: 计算属性会缓存
getter
的计算结果。 - 更新: 当
getter
依赖的state
发生变化时,Vue 会通知计算属性重新计算,并更新缓存。
用一张表格来总结一下:
步骤 | 描述 | 涉及的源码 |
---|---|---|
定义 | 在 Vuex 的 store 中定义 getters 。 |
store.js |
包装 | Vuex 会对 getters 进行包装,注入 store 实例以及模块相关的 state 和 getters 。 |
src/module/module-collection.js -> installModule |
登记 | Vuex 会将包装后的 getters 转换为计算属性,注册到 Vue 实例上。 |
src/store.js -> resetStoreVM |
访问 | 当我们访问 store.getters.xxx 时,实际上是在访问 Vue 实例上的计算属性。 |
N/A |
依赖收集 | Vue 的响应式系统会自动追踪 getter 函数所依赖的 state 。 |
Vue 的响应式系统 (不在 Vuex 源码里,而是 Vue 的核心机制) |
缓存 | 计算属性会缓存 getter 的计算结果。 |
Vue 的计算属性 (不在 Vuex 源码里,而是 Vue 的核心机制) |
更新 | 当 getter 依赖的 state 发生变化时,Vue 会通知计算属性重新计算,并更新缓存。 |
Vue 的响应式系统 + 计算属性 (不在 Vuex 源码里,而是 Vue 的核心机制) |
Getters 的妙用:一些栗子
光说不练假把式,来几个实际的例子:
-
购物车总价:
// store.js state: { cart: [ { id: 1, price: 10, quantity: 2 }, { id: 2, price: 20, quantity: 1 } ] }, getters: { totalPrice: (state) => { return state.cart.reduce((total, item) => total + item.price * item.quantity, 0) } }
在组件里:
<template> <div> 总价:{{ totalPrice }} </div> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters(['totalPrice']) } } </script>
-
过滤列表:
// store.js state: { todos: [ { id: 1, text: '吃饭', done: true }, { id: 2, text: '睡觉', done: false }, { id: 3, text: '打豆豆', done: false } ] }, getters: { doneTodos: (state) => { return state.todos.filter(todo => todo.done) } }
在组件里:
<template> <ul> <li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li> </ul> </template> <script> import { mapGetters } from 'vuex' export default { computed: { ...mapGetters(['doneTodos']) } } </script>
这些例子展示了
getters
的强大之处,它可以将复杂的计算逻辑从组件中抽离出来,集中管理,并且具有依赖收集和缓存的特性,提高了应用的性能。
总结陈词:Getters,Vuex 的得力助手
getters
是 Vuex 中一个非常重要的概念,它允许我们从 state
中派生出新的数据,并且具有依赖收集和缓存机制。 理解 getters
的实现原理,可以帮助我们更好地利用 Vuex,编写出更高效、更可维护的应用。
下次面试的时候,如果面试官问你 Vuex 的 getters
是怎么实现的,你就可以把今天学到的知识滔滔不绝地讲给他听,保证让他对你刮目相看!
好了,今天的讲座就到这里,感谢各位的收听! 咱们下期再见!