【源碼】1142- 一文讀懂Vuex4源碼
Vuex4
Vuex是在Vue中常用的狀態(tài)管理庫,在Vue3發(fā)布后,這個狀態(tài)管理庫也隨之發(fā)出了適配Vue3的Vuex4
快速過Vuex3.x原理
為什么每個組件都可以通過 this.$store訪問到store數(shù)據(jù)?在beforeCreate時,通過mixin的方式注入了store 為什么Vuex中的數(shù)據(jù)都是響應式的 創(chuàng)建store的時候調用的是 new Vue,創(chuàng)建了一個Vue實例,相當于借用了Vue的響應式。mapXxxx是怎么獲取到store中的數(shù)據(jù)和方法的 mapXxxx只是一個語法糖,底層實現(xiàn)也是從$store中獲取然后返回到computed / methods中。
Vuex4使用
Vue.useStore
在Vue3 Composition API中使用Vuex
import?{?useStore?}?from?'vuex'
export?default{
????setup(){
????????const?store?=?useStore();
????}
}
Vuex4原理探究
去除冗余代碼看本質
Vuex4是怎么注入Vue的
install
Vuex是以插件的形式在Vue中使用的,在createApp時調用install安裝 插件列表中加入plugin 執(zhí)行plugin的安裝函數(shù) 也就是我們常用的Vue.use函數(shù)
//?Vue3源碼?app.use
export?function?createAppAPI<HostElement>(
??render:?RootRenderFunction,
??hydrate?:?RootHydrateFunction
):?CreateAppFunction<HostElement>?{
??return?function?createApp(rootComponent,?rootProps?=?null)?{
??
????//?省略部分代碼....
????const?app:?App?=?(context.app?=?{
??????_uid:?uid++,
??????_component:?rootComponent?as?ConcreteComponent,
??????_props:?rootProps,
??????_container:?null,
??????_context:?context,
??????version,
??????
??????//?省略部分代碼....
??????use(plugin:?Plugin,?...options:?any[])?{
????????if?(installedPlugins.has(plugin))?{
??????????__DEV__?&&?warn(`Plugin?has?already?been?applied?to?target?app.`)
????????}?else?if?(plugin?&&?isFunction(plugin.install))?{
??????????installedPlugins.add(plugin)
??????????plugin.install(app,?...options)
????????}?else?if?(isFunction(plugin))?{
??????????installedPlugins.add(plugin)
??????????plugin(app,?...options)
????????}?else?if?(__DEV__)?{
??????????warn(
????????????`A?plugin?must?either?be?a?function?or?an?object?with?an?"install"?`?+
??????????????`function.`
??????????)
????????}
????????return?app
??????},
??????//?省略部分代碼?....
???}
}
Store 類的install,兩種實現(xiàn)分別為掛載到全局和組件內訪問 將store掛載到全局properties 詳情見下文app.provide講解 實現(xiàn)通過inject獲取 實現(xiàn)this.$store獲取
//?Vuex4實現(xiàn)插件install
install?(app,?injectKey)?{
??//?實現(xiàn)通過inject獲取
??app.provide(injectKey?||?storeKey,?this)
??//?實現(xiàn)this.$store獲取
??app.config.globalProperties.$store?=?this
Provide / Inject架構示意圖

下面接著看provide實現(xiàn)
app.provide實現(xiàn)
每個Vue組件都有一個context上下文對象 對context上下文中的provides對象進行賦值 createAppContext是一個創(chuàng)建App上下文函數(shù) 將插件通過key / value的形式掛載到app上下文的provides對象上 inject時,通過存入的key進行取出 返回體中是一個具有一些常見的Option(mixins、components等) Vue的插件實現(xiàn)最主要的為其中一項provides,具體實現(xiàn)方式為:
//?Vue3?app.provide實現(xiàn)
provide(key,?value)?{
??//?已存在則警告
??if?(__DEV__?&&?(key?as?string?|?symbol)?in?context.provides)?{
????warn(
??????`App?already?provides?property?with?key?"${String(key)}".?`?+
????????`It?will?be?overwritten?with?the?new?value.`
????)
??}
??//?將store放入context的provide中
??context.provides[key?as?string]?=?value
??return?app
}
//?context相關???context為上下文對象
const?context?=?createAppContext()
export?function?createAppContext():?AppContext?{
??return?{
????app:?null?as?any,
????config:?{
??????isNativeTag:?NO,
??????performance:?false,
??????globalProperties:?{},
??????optionMergeStrategies:?{},
??????errorHandler:?undefined,
??????warnHandler:?undefined,
??????compilerOptions:?{}
????},
????mixins:?[],
????components:?{},
????directives:?{},
????provides:?Object.create(null)
??}
}
useStore的實現(xiàn)
function?useStore?(key?=?null)?{
??return?inject(key?!==?null???key?:?storeKey)
}
Vue.provide
Vue的provide API也比較簡單,相當于直接通過key/value賦值 當前實例provides和父級實例provides相同時,通過原型鏈建立連接
//?Vue3?provide實現(xiàn)
function?provide<T>(key:?InjectionKey?|?string?|?number,?value:?T )?{
??if?(!currentInstance)?{
????if?(__DEV__)?{
??????warn(`provide()?can?only?be?used?inside?setup().`)
????}
??}?else?{
????let?provides?=?currentInstance.provides
????const?parentProvides?=
??????currentInstance.parent?&&?currentInstance.parent.provides
????if?(parentProvides?===?provides)?{
??????provides?=?currentInstance.provides?=?Object.create(parentProvides)
????}
????//?TS?doesn't?allow?symbol?as?index?type
????provides[key?as?string]?=?value
??}
}
Vue.inject
通過provide時存入的key取出store 有父級實例則取父級實例的provides,沒有則取根實例的provides
//?Vue3?inject實現(xiàn)
function?inject(
??key:?InjectionKey<any>?|?string,
??defaultValue?:?unknown,
??treatDefaultAsFactory?=?false
)?{
??const?instance?=?currentInstance?||?currentRenderingInstance
??if?(instance)?{
????//?有父級實例則取父級實例的provides,沒有則取根實例的provides
????const?provides?=
??????instance.parent?==?null
??????????instance.vnode.appContext?&&?instance.vnode.appContext.provides
????????:?instance.parent.provides
????//?通過provide時存入的key取出store
????if?(provides?&&?(key?as?string?|?symbol)?in?provides)?{
??????return?provides[key?as?string]
????}
????//?省略一部分代碼......
??}?
}
注入
為什么每個組件實例都有Store對象了? 優(yōu)先注入父級provides 兜底為注入app上下文的provides 在創(chuàng)建組件實例的時候注入了provides
function?createComponentInstance(vnode,?parent,?suspense)?{
????const?type?=?vnode.type;
????const?appContext?=?(parent???parent.appContext?:?vnode.appContext)?||?emptyAppContext;
????const?instance?=?{
????????parent,
????????appContext,
????????//?...
????????provides:?parent???parent.provides?:?Object.create(appContext.provides),
????????//?...
????}
????//?...
????return?instance;
}
可從vue中引入provide、inject、getCurrentInstance等API進行庫開發(fā) / 高階用法,這里不過多贅述。
Vuex4執(zhí)行機制
createStore
從createStore開始看起 可以發(fā)現(xiàn)Vuex4中的state是通過reactive API去創(chuàng)建的響應式數(shù)據(jù),Vuex3中是通過new Vue實例 dispatch、commit的實現(xiàn)基本是封裝了一層執(zhí)行,底層也是通過store去執(zhí)行,不用過于關心 而Vuex4的響應式實現(xiàn),同樣是借用了Vue3的響應式API reactive
//?Vuex4源碼
export?function?createStore?(options)?{
????return?new?Store(options)
}
class?Store{
????constructor?(options?=?{}){
????????//?省略若干代碼...
????????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._makeLocalGettersCache?=?Object.create(null)
????????
????????//?bind?commit?and?dispatch?to?self
????????const?store?=?this
????????const?{?dispatch,?commit?}?=?this
????????this.dispatch?=?function?boundDispatch?(type,?payload)?{
??????????return?dispatch.call(store,?type,?payload)
????????}????
????????this.commit?=?function?boundCommit?(type,?payload,?options)?{
??????????return?commit.call(store,?type,?payload,?options)
????????}
????????
????????
????????const?state?=?this._modules.root.state
????????installModule(this,?state,?[],?this._modules.root);
????????resetStoreState(this,?state)
??????
????????//?省略若干代碼...
????}
}
function?resetStoreState?(store,?state,?hot)?{
????//?省略若干代碼...
????store._state?=?reactive({
????????data:?state
????})
????//?省略若干代碼...
}
installModule
installModule主要為按序初始化各模塊,主要功能代碼已高亮
Mutation
Action
Getter
Child(install)
//?Vuex4
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}?for?the?namespaced?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(()?=>?{
??????if?(__DEV__)?{
????????if?(moduleName?in?parentState)?{
??????????console.warn(
????????????`[vuex]?state?field?"${moduleName}"?was?overridden?by?a?module?with?the?same?name?at?"${path.join('.')}"`
??????????)
????????}
??????}
??????parentState[moduleName]?=?module.state
????})
??}
??const?local?=?module.context?=?makeLocalContext(store,?namespace,?path)
??module.forEachMutation((mutation,?key)?=>?{
????const?namespacedType?=?namespace?+?key
????registerMutation(store,?namespacedType,?mutation,?local)
??})
??module.forEachAction((action,?key)?=>?{
????const?type?=?action.root???key?:?namespace?+?key
????const?handler?=?action.handler?||?action
????registerAction(store,?type,?handler,?local)
??})
??module.forEachGetter((getter,?key)?=>?{
????const?namespacedType?=?namespace?+?key
????registerGetter(store,?namespacedType,?getter,?local)
??})
??module.forEachChild((child,?key)?=>?{
????installModule(store,?rootState,?path.concat(key),?child,?hot)
??})
}
訂閱機制
看完了Vuex4是如何安裝和注入的,最后來看看Vuex的訂閱機制是如何實現(xiàn)的
和訂閱機制有關的方法主要有 訂閱:subscribe、subscribeAction,分別用于訂閱Mutation和Action 執(zhí)行:commit、dispatch,分別用于執(zhí)行 數(shù)據(jù)項有:_actionSubscribers、_subscribers
subscribe
訂閱 store 的 mutation。
handler會在每個 mutation 完成后調用,接收 mutation 和經(jīng)過 mutation 后的狀態(tài)作為參數(shù)
所有的訂閱callback都會被放入
this._subscribers,可通過prepend選項選擇放入隊頭 / 隊尾。
將callback推入訂閱數(shù)組 返回一個取消訂閱的函數(shù)
//?用法???該方法會返回一個取消訂閱的函數(shù)
store.subscribe((action,?state)?=>?{
??console.log(action.type)
??console.log(action.payload)
},?{?prepend:?true?})?
//?subscribe??Vuex4源碼實現(xiàn)
subscribe?(fn,?options)?{
??return?genericSubscribe(fn,?this._subscribers,?options)
}
function?genericSubscribe?(fn,?subs,?options)?{
??if?(subs.indexOf(fn)?0)?{
????options?&&?options.prepend
????????subs.unshift(fn)
??????:?subs.push(fn)
??}
??return?()?=>?{
????const?i?=?subs.indexOf(fn)
????if?(i?>?-1)?{
??????subs.splice(i,?1)
????}
??}
}
接著看看commit執(zhí)行時如何觸發(fā)這些訂閱的callback

執(zhí)行需commit的函數(shù) 依次執(zhí)行 this._subscribers中的訂閱callback
//?commit實現(xiàn)
commit?(_type,?_payload,?_options)?{
??//?check?object-style?commit
??const?{
????type,
????payload,
????options
??}?=?unifyObjectStyle(_type,?_payload,?_options)
??const?mutation?=?{?type,?payload?}
??const?entry?=?this._mutations[type]
??//?執(zhí)行需commit的函數(shù)
??this._withCommit(()?=>?{
????entry.forEach(function?commitIterator?(handler)?{
??????handler(payload)
????})
??})x?
????//?執(zhí)行訂閱函數(shù)
??this._subscribers
????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
????.forEach(sub?=>?sub(mutation,?this.state))
????
????//?省略若干代碼....
}
subscribeAction
訂閱 store 的 action。
handler會在每個 action 分發(fā)的時候調用并接收 action 描述和當前的 store 的 state 這兩個參數(shù)
可訂閱:執(zhí)行前、執(zhí)行后和錯誤
將訂閱對象推入 this._actionSubscribers返回一個取消訂閱函數(shù)
//?用法
store.subscribeAction({
??before:?(action,?state)?=>?{
????console.log(`before?action?${action.type}`)
??},
??after:?(action,?state)?=>?{
????console.log(`after?action?${action.type}`)
??},
??error:?(action,?state,?error)?=>?{
????console.log(`error?action?${action.type}`)
????console.error(error)
??}
},?{?prepend:?true?})
//?Vuex4源碼實現(xiàn)
subscribeAction?(fn,?options)?{
??const?subs?=?typeof?fn?===?'function'???{?before:?fn?}?:?fn
??return?genericSubscribe(subs,?this._actionSubscribers,?options)
}
function?genericSubscribe?(fn,?subs,?options)?{
??if?(subs.indexOf(fn)?0)?{
????options?&&?options.prepend
????????subs.unshift(fn)
??????:?subs.push(fn)
??}
??return?()?=>?{
????const?i?=?subs.indexOf(fn)
????if?(i?>?-1)?{
??????subs.splice(i,?1)
????}
??}
}
dispatch執(zhí)行時如何觸發(fā)這些訂閱函數(shù)?

//?Vuex4源碼實現(xiàn)
dispatch?(_type,?_payload)?{
??//?check?object-style?dispatch
??const?{
????type,
????payload
??}?=?unifyObjectStyle(_type,?_payload)
??const?action?=?{?type,?payload?}
??const?entry?=?this._actions[type]
??if?(!entry)?{
????if?(__DEV__)?{
??????console.error(`[vuex]?unknown?action?type:?${type}`)
????}
????return
??}
??//?before訂閱執(zhí)行
??try?{
????this._actionSubscribers
??????.slice()?//?shallow?copy?to?prevent?iterator?invalidation?if?subscriber?synchronously?calls?unsubscribe
??????.filter(sub?=>?sub.before)
??????.forEach(sub?=>?sub.before(action,?this.state))
??}?catch?(e)?{
????if?(__DEV__)?{
??????console.warn(`[vuex]?error?in?before?action?subscribers:?`)
??????console.error(e)
????}
??}
??//?action執(zhí)行
??const?result?=?entry.length?>?1
??????Promise.all(entry.map(handler?=>?handler(payload)))
????:?entry[0](payload)
??return?new?Promise((resolve,?reject)?=>?{
????result.then(res?=>?{
????????//?after訂閱執(zhí)行
??????try?{
????????this._actionSubscribers
??????????.filter(sub?=>?sub.after)
??????????.forEach(sub?=>?sub.after(action,?this.state))
??????}?catch?(e)?{
????????if?(__DEV__)?{
??????????console.warn(`[vuex]?error?in?after?action?subscribers:?`)
??????????console.error(e)
????????}
??????}
??????resolve(res)
????},?error?=>?{
????????//?error訂閱執(zhí)行
??????try?{
????????this._actionSubscribers
??????????.filter(sub?=>?sub.error)
??????????.forEach(sub?=>?sub.error(action,?this.state,?error))
??????}?catch?(e)?{
????????if?(__DEV__)?{
??????????console.warn(`[vuex]?error?in?error?action?subscribers:?`)
??????????console.error(e)
????????}
??????}
??????reject(error)
????})
??})
}
一句話總結
Vuex3 ?-> Vuex4,主要實現(xiàn)方式將mixin注入改為了provides / inject的方式注入。
Provide / Inject 不僅用于Vuex實現(xiàn),同樣可以用于深層組件的數(shù)據(jù)傳遞
提示:
provide?和?inject?綁定并不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的 property 還是可響應的。

回復“加群”與大佬們一起交流學習~
點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章
