<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【源碼】1142- 一文讀懂Vuex4源碼

          共 12116字,需瀏覽 25分鐘

           ·

          2021-11-15 20:33

          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主要為按序初始化各模塊,主要功能代碼已高亮

          1. Mutation

          2. Action

          3. Getter

          4. 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選項選擇放入隊頭 / 隊尾。

          1. 將callback推入訂閱數(shù)組
          2. 返回一個取消訂閱的函數(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

          1. 執(zhí)行需commit的函數(shù)
          2. 依次執(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í)行后和錯誤

          1. 將訂閱對象推入this._actionSubscribers
          2. 返回一個取消訂閱函數(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 還是可響應的。

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設計模式 重溫系列(9篇全)
          4.?正則 / 框架 / 算法等 重溫系列(16篇全)
          5.?Webpack4 入門(上)||?Webpack4 入門(下)
          6.?MobX 入門(上)?||??MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復“加群”與大佬們一起交流學習~

          點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  120分钟婬片免费看 | 国产激情三级在线观看 | 高清国产AV | 日本无码一区二区三区 | 国产伦子伦一级A片在线 |