一文讀懂vuex4源碼,原來provide/inject就是妙用了原型鏈?

1. 前言

你好,我是若川,歡迎加我微信ruochuan12,加群長期交流學習。

這是學習源碼整體架構系列 之 vuex4 源碼(第十篇)。學習源碼整體架構系列文章(有哪些必看的JS庫):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux、vue-devtools 直接打開文件功能揭秘。

本文倉庫地址[1]git clone https://github.com/lxchuan12/vuex4-analysis.git,本文最佳閱讀方式,克隆倉庫自己動手調試,容易吸收消化。

要是有人說到怎么讀源碼,正在讀文章的你能推薦我的源碼系列文章,那真是無以為報啊

我的文章,盡量寫得讓想看源碼又不知道怎么看的讀者能看懂。我都是推薦使用搭建環境斷點調試源碼學習哪里不會點哪里邊調試邊看,而不是硬看。正所謂:授人與魚不如授人予漁

閱讀本文后你將學到:

  • git subtree 管理子倉庫

    如何學習Vuex 4源碼、理解Vuex原理

    Vuex 4Vuex 3 的異同

    Vuex 4 composition API 如何使用

    Vue.provide / Vue.inject API 使用和原理

    如何寫一個 Vue3 插件

    等等

如果對于谷歌瀏覽器調試還不是很熟悉的讀者,可以看這篇文章chrome devtools source面板,寫的很詳細。順帶提一下,我打開的設置,source面板中支持展開搜索代碼塊(默認不支持),一圖勝千言。

谷歌瀏覽器是我們前端常用的工具,所以建議大家深入學習,畢竟工欲善其事,必先利其器

之前寫過Vuex 3的源碼文章學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫[2],倉庫有很詳細的注釋和看源碼方法,所以本文不會過多贅述與Vuex 3源碼相同的地方。

1.1 本文閱讀最佳方式

把我的vuex4源碼倉庫 git clone https://github.com/lxchuan12/vuex4-analysis.git克隆下來,順便star一下我的vuex4源碼學習倉庫[3]^_^。跟著文章節奏調試和示例代碼調試,用chrome動手調試印象更加深刻。文章長段代碼不用細看,可以調試時再細看。看這類源碼文章百遍,可能不如自己多調試幾遍,大膽猜測,小心求證。也歡迎加我微信交流ruochuan12

2. Vuex 原理簡述

結論先行Vuex原理可以拆解為三個關鍵點。第一點、其實就是每個組件實例里都注入了Store實例。第二點、Store實例中的各種方法都是為Store中的屬性服務的。第三點、Store中的屬性變更觸發視圖更新。

本文主要講解第一點。第二點在我的上一篇文章學習 vuex 源碼整體架構,打造屬于自己的狀態管理庫詳細講了,本文就不贅述了。第三點兩篇文章都沒有詳細講述。

以下是一段簡短的代碼說明Vuex原理的。

//?簡版
class?Store{constructor(){this._state?=?'Store?實例';}dispatch(val){this.__state?=?val;}commit(){}//?省略
}const?store?=?new?Store();
var?rootInstance?=?{parent:?null,provides:?{store:?store,},
};
var?parentInstance?=?{parent:?rootInstance,provides:?{store:?store,}
};
var?childInstance1?=?{parent:?parentInstance,provides:?{store:?store,}
};
var?childInstance2?=?{parent:?parentInstance,provides:?{store:?store,}
};store.dispatch('我被修改了');
//?store?Store?{_state:?"我被修改了"}// rootInstance、parentInstance、childInstance1、childInstance2 這些對象中的provides.store都改了。
//?因為共享著同一個store對象。
provide,inject示例圖

看了上面的官方文檔中的圖,大概知道是用provide父級組件中提供Store實例,用inject來獲取到Store實例。

那么接下來,帶著問題:

1、為什么修改了實例store里的屬性,變更后會觸發視圖更新。
2、Vuex4作為Vue的插件如何實現和Vue結合的。
3、provideinject的如何實現的,每個組件如何獲取到組件實例中的Store的。
4、為什么每個組件對象里都有Store實例對象了(渲染組件對象過程)。
5、為什么在組件中寫的provide提供的數據,能被子級組件獲取到。

3. Vuex 4 重大改變

在看源碼之前,先來看下Vuex 4發布的release和官方文檔遷移提到的重大改變,Vuex 4 release[4]

從 3.x 遷移到 4.0[5]

Vuex 4的重點是兼容性。Vuex 4支持使用Vue 3開發,并且直接提供了和Vuex 3完全相同的API,因此用戶可以在Vue 3項目中復用現有的Vuex代碼。

相比Vuex 3版本。主要有如下重大改變(其他的在上方鏈接中):

3.1 安裝過程

Vuex 3Vue.use(Vuex)

Vuex 4則是app.use(store)

import?{?createStore?}?from?'vuex'export?const?store?=?createStore({state()?{return?{count:?1}}
})
import?{?createApp?}?from?'vue'
import?{?store?}?from?'./store'
import?App?from?'./App.vue'const?app?=?createApp(App)app.use(store)app.mount('#app')

3.2 核心模塊導出了 createLogger 函數

import?{?createLogger?}?from?'vuex'

接下來我們從源碼的角度來看這些重大改變

4. 從源碼角度看 Vuex 4 重大變化

4.1 chrome 調試 Vuex 4 源碼準備工作

git?subtree?add?--prefix=vuex?https://github.com/vuejs/vuex.git?4.0

這種方式保留了vuex4倉庫的git記錄信息。更多git subtree使用方式可以查看這篇文章用 Git Subtree 在多個 Git 項目間雙向同步子項目,附簡明使用手冊[6]

作為讀者朋友的你,只需克隆我的`Vuex 4`源碼倉庫[7] https://github.com/lxchuan12/vuex4-analysis.git 即可,也歡迎star一下。

vuex/examples/webpack.config.js,加個devtool: 'source-map',這樣就能開啟sourcemap調試源碼了。

我們使用項目中的購物車的例子調試,貫穿全文。

git?clone?https://github.com/lxchuan12/vuex4-analysis.git
cd?vuex
npm?i
npm?run?dev
#?打開?http://localhost:8080/
#?選擇?composition??購物車的例子?shopping-cart
#?打開?http://localhost:8080/composition/shopping-cart/
#?按?F12?打開調試工具,source面板?=>?page?=>?webpack://?=>?.

據說一圖勝千言,這時簡單截個調試的圖。

vuex debugger

找到 createStore函數打上斷點。

//?webpack:///./examples/composition/shopping-cart/store/index.js
import?{?createStore,?createLogger?}?from?'vuex'
import?cart?from?'./modules/cart'
import?products?from?'./modules/products'const?debug?=?process.env.NODE_ENV?!==?'production'export?default?createStore({modules:?{cart,products},strict:?debug,plugins:?debug???[createLogger()]?:?[]
})

找到app.js入口,在app.use(store)app.mount('#app')等打上斷點。

//?webpack:///./examples/composition/shopping-cart/app.js
import?{?createApp?}?from?'vue'
import?App?from?'./components/App.vue'
import?store?from?'./store'
import?{?currency?}?from?'./currency'const?app?=?createApp(App)app.use(store)app.mount('#app')

接下來,我們從createApp({})app.use(Store)兩個方面發散開來講解。

4.2 Vuex.createStore 函數

相比 Vuex 3 中,new Vuex.Store,其實是一樣的。只不過為了和Vue 3 統一,Vuex 4 額外多了一個 createStore 函數。

export?function?createStore?(options)?{return?new?Store(options)
}
class?Store{constructor?(options?=?{}){//?省略若干代碼...this._modules?=?new?ModuleCollection(options)const?state?=?this._modules.root.stateresetStoreState(this,?state)//?省略若干代碼...}
}
function?resetStoreState?(store,?state,?hot)?{//?省略若干代碼...store._state?=?reactive({data:?state})//?省略若干代碼...
}

監測數據

Vuex 3不同的是,監聽數據不再是用new Vue(),而是Vue 3提供的reactive方法。

Vue.reactive 函數方法,本文就不展開講解了。因為展開來講,又可以寫篇新的文章了。只需要知道主要功能是監測數據改變,變更視圖即可。

這也就算解答了開頭提出的第一個問題。

跟著斷點我們繼續看app.use()方法,Vue提供的插件機制。

4.3 app.use() 方法

use做的事情說起來也算簡單,把傳遞過來的插件添加插件集合中,到防止重復。

執行插件,如果是對象,install是函數,則把參數app和其他參數傳遞給install函數執行。如果是函數直接執行。

//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function?createAppAPI(render,?hydrate)?{return?function?createApp(rootComponent,?rootProps?=?null)?{//?代碼有刪減const?installedPlugins?=?new?Set();const?app?=?(context.app?=?{use(plugin,?...options)?{//?已經有插件,并且?不是生產環境,報警告。if?(installedPlugins.has(plugin))?{(process.env.NODE_ENV?!==?'production')?&&?warn(`Plugin?has?already?been?applied?to?target?app.`);}//?插件的install?是函數,則添加插件,并執行?install?函數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?((process.env.NODE_ENV?!==?'production'))?{warn(`A?plugin?must?either?be?a?function?or?an?object?with?an?"install"?`?+`function.`);}//?支持鏈式調用return?app;},provide(){?//?省略...?后文再講}});}
}

上面代碼中,斷點這行plugin.install(app, ...options);

跟著斷點走到下一步,install函數。

4.4 install 函數

export?class?Store{//?省略若干代碼...install?(app,?injectKey)?{//?為?composition?API?中使用//??可以傳入?injectKey??如果沒傳取默認的?storeKey?也就是?storeapp.provide(injectKey?||?storeKey,?this)//?為?option?API?中使用app.config.globalProperties.$store?=?this}//?省略若干代碼...
}

Vuex4中的install函數相對比Vuex3中簡單了許多。第一句是給Composition API提供的。注入到根實例對象中。第二句則是為option API提供的。

接著斷點這兩句,按F11來看app.provide實現。

4.4.1 app.provide

簡單來說就是給contextprovides屬性中加了store = Store實例對象

provide(key,?value)?{//?如果已經有值了警告if?((process.env.NODE_ENV?!==?'production')?&&?key?in?context.provides)?{warn(`App?already?provides?property?with?key?"${String(key)}".?`?+`It?will?be?overwritten?with?the?new?value.`);}//?TypeScript?doesn't?allow?symbols?as?index?type//?https://github.com/Microsoft/TypeScript/issues/24587context.provides[key]?=?value;return?app;
}

接著從上方代碼中搜索context,可以發現這一句代碼:

const?context?=?createAppContext();

接著我們來看函數 createAppContextcontext 為上下文

//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function?createAppContext()?{return?{app:?null,config:?{isNativeTag:?NO,performance:?false,globalProperties:?{},optionMergeStrategies:?{},isCustomElement:?NO,errorHandler:?undefined,warnHandler:?undefined},mixins:?[],components:?{},directives:?{},provides:?Object.create(null)};
}

Vue3 文檔應用配置(app.config)[8]

4.4.2 app.config.globalProperties

app.config.globalProperties 官方文檔[9]

用法:

app.config.globalProperties.$store?=?{}app.component('child-component',?{mounted()?{console.log(this.$store)?//?'{}'}
})

也就能解釋為什么每個組件都可以使用 this.$store.xxx 訪問 vuex中的方法和屬性了。

也就是說在appContext.provides中注入了一個Store實例對象。這時也就是相當于根組件實例和config全局配置globalProperties中有了Store實例對象

至此我們就看完,createStore(store)app.use(store)兩個API

app.provide 其實是用于composition API使用的。

但這只是文檔中這樣說的,為什么就每個組件實例都能訪問的呢,我們繼續深入探究下原理。

接下來,我們看下源碼具體實現,為什么每個組件實例中都能獲取到的。

這之前先來看下組合式API中,我們如何使用Vuex4,這是線索。

4.5 composition API 中如何使用Vuex 4

接著我們找到如下文件,useStore是我們斷點的對象。

//?webpack:///./examples/composition/shopping-cart/components/ShoppingCart.vue
import?{?computed?}?from?'vue'
import?{?useStore?}?from?'vuex'
import?{?currency?}?from?'../currency'export?default?{setup?()?{const?store?=?useStore()//?我加的這行代碼window.ShoppingCartStore?=?store;//?省略了若干代碼}
}

接著斷點按F11,單步調試,會發現最終是使用了Vue.inject方法。

4.5.1 Vuex.useStore 源碼實現

//?vuex/src/injectKey.js
import?{?inject?}?from?'vue'export?const?storeKey?=?'store'export?function?useStore?(key?=?null)?{return?inject(key?!==?null???key?:?storeKey)
}

4.5.2 Vue.inject 源碼實現

接著看inject函數,看著代碼很多,其實原理很簡單,就是要找到我們用provide提供的值。

如果沒有父級,也就是根實例,就取實例對象中的vnode.appContext.provides。否則就取父級中的instance.parent.provides的值。

Vuex4源碼里則是:Store實例對象。

//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function?inject(key,?defaultValue,?treatDefaultAsFactory?=?false)?{//?fallback?to?`currentRenderingInstance`?so?that?this?can?be?called?in//?a?functional?component//?如果是被一個函數式組件調用則取?currentRenderingInstanceconst?instance?=?currentInstance?||?currentRenderingInstance;if?(instance)?{//?#2400//?to?support?`app.use`?plugins,//?fallback?to?appContext's?`provides`?if?the?intance?is?at?rootconst?provides?=?instance.parent?==?null??instance.vnode.appContext?&&?instance.vnode.appContext.provides:?instance.parent.provides;if?(provides?&&?key?in?provides)?{//?TS?doesn't?allow?symbol?as?index?typereturn?provides[key];}//?如果參數大于1個?第二個則是默認值?,第三個參數是 true,并且第二個值是函數則執行函數。else?if?(arguments.length?>?1)?{return?treatDefaultAsFactory?&&?isFunction(defaultValue)??defaultValue():?defaultValue;}//?警告沒找到else?if?((process.env.NODE_ENV?!==?'production'))?{warn(`injection?"${String(key)}"?not?found.`);}}//?如果沒有當前實例則說明則報警告。//?也就是是說inject必須在setup中調用或者在函數式組件中使用else?if?((process.env.NODE_ENV?!==?'production'))?{warn(`inject()?can?only?be?used?inside?setup()?or?functional?components.`);}
}

接著我們繼續來看inject的相對應的provide

4.5.3 ?Vue.provide 源碼實現

provide函數作用其實也算簡單,1、也就是給當前組件實例上的provides對象屬性,添加鍵值對key/value

2、還有一個作用是當當前組件和父級組件的provides相同時,在當前組件實例中的provides對象和父級,則建立鏈接,也就是原型[[prototype]],(__proto__)。

//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function?provide(key,?value)?{if?(!currentInstance)?{if?((process.env.NODE_ENV?!==?'production'))?{warn(`provide()?can?only?be?used?inside?setup().`);}}else?{let?provides?=?currentInstance.provides;//?by?default?an?instance?inherits?its?parent's?provides?object//?but?when?it?needs?to?provide?values?of?its?own,?it?creates?its//?own?provides?object?using?parent?provides?object?as?prototype.//?this?way?in?`inject`?we?can?simply?look?up?injections?from?direct//?parent?and?let?the?prototype?chain?do?the?work.const?parentProvides?=?currentInstance.parent?&&?currentInstance.parent.provides;if?(parentProvides?===?provides)?{provides?=?currentInstance.provides?=?Object.create(parentProvides);}//?TS?doesn't?allow?symbol?as?index?typeprovides[key]?=?value;}
}

provide函數中的這段,可能不是那么好理解。

if?(parentProvides?===?provides)?{provides?=?currentInstance.provides?=?Object.create(parentProvides);
}

我們來舉個例子消化一下。

var?currentInstance?=?{?provides:?{?store:?{?__state:?'Store實例'?}??}?};
var?provides?=?currentInstance.provides;
//?這句是我手動加的,在后文中則是創建實例時就是寫的同一個對象,當然就會相等了。
var?parentProvides?=?provides;
if(parentProvides?===?provides){provides?=??currentInstance.provides?=?Object.create(parentProvides);
}

經過一次執行這個后,currentInstance 就變成了這樣。

{provides:?{//?可以容納其他屬性,比如用戶自己寫的__proto__?:?{?store:?{?__state:?'Store實例'?}??}}
}

執行第二次時,currentInstance 則是:

{provides:?{//?可以容納其他屬性,比如用戶自己寫的__proto__:?{//?可以容納其他屬性,比如用戶自己寫的__proto__?:?{?store:?{?__state:?'Store實例'?}??}}}
}

以此類推,多執行provide幾次,原型鏈就越長。

上文injectprovide函數中都有個變量currentInstance當前實例,那么當前實例對象是怎么來的呢。

為什么每個組件就能訪問到,依賴注入的思想。有一個討巧的方法,就是在文件runtime-core.esm-bundler.js中搜索provides,則能搜索到createComponentInstance函數

接下來我們createComponentInstance函數如何創建組件實例。

4.6 createComponentInstance 創建組件實例

可以禁用其他斷點,單獨斷點這里, 比如:const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;來看具體實現。

//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
const?emptyAppContext?=?createAppContext();
let?uid$1?=?0;
function?createComponentInstance(vnode,?parent,?suspense)?{const?type?=?vnode.type;const?appContext?=?(parent???parent.appContext?:?vnode.appContext)?||?emptyAppContext;const?instance?=?{uid:?uid$1++,vnode,type,parent,appContext,root:?null,next:?null,subTree:?null,//?...provides:?parent???parent.provides?:?Object.create(appContext.provides),//?...}instance.root?=?parent???parent.root?:?instance;//?...return?instance;
}

斷點時會發現,根組件實例時vnode已經生成,至于是什么時候生成的,我整理了下簡化版。

//?把上文中的?appContext?賦值給了?`appContext`
mount(rootContainer,?isHydrate)?{if?(!isMounted)?{const?vnode?=?createVNode(rootComponent,?rootProps);//?store?app?context?on?the?root?VNode.//?this?will?be?set?on?the?root?instance?on?initial?mount.vnode.appContext?=?context;}
},

其中 Object.create 其實就是建立原型關系。這時放一張圖,一圖勝千言。

直觀的圖

出自黃軼老師拉勾專欄,本想自己畫一張圖,但覺得這張挺好的。

4.6.1 組件實例生成了,那怎么把它們結合呢

這時,也有一個討巧的方法,在runtime-core.esm-bundler.js文件中,搜索 provide(可以搜到如下代碼:

這段代碼其實看起來很復雜的樣子,實際上主要就是把用戶在組件中寫的provides對象或者函數返回值遍歷, 生成類似這樣的實例對象:

//?當前組件實例
{parent:?'父級的實例',provides:?{//?可以容納其他屬性,比如用戶自己寫的__proto__:?{//?可以容納其他屬性,比如用戶自己寫的__proto__?:?{?store:?{?__state:?'Store實例'?}??}}}
}
//?webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function?applyOptions(instance,?options,?deferredData?=?[],?deferredWatch?=?[],?deferredProvide?=?[],?asMixin?=?false)?{//?...if?(provideOptions)?{deferredProvide.push(provideOptions);}if?(!asMixin?&&?deferredProvide.length)?{deferredProvide.forEach(provideOptions?=>?{//?組件中寫?provides?可以是對象或者是函數const?provides?=?isFunction(provideOptions)??provideOptions.call(publicThis):?provideOptions;Reflect.ownKeys(provides).forEach(key?=>?{provide(key,?provides[key]);});});}//?...
}

這樣一來就從上到下app.provide提供的對象,被注入到每一個組件實例中了。同時組件本身提供的provides也被注入到實例中了。

接著我們跟著項目來驗證下,上文中的表述。翻看Vue3文檔可以發現有一個API可以獲取當前組件實例。

4.7 getCurrentInstance 獲取當前實例對象

getCurrentInstance 支持訪問內部組件實例,用于高階用法或庫的開發。

import?{?getCurrentInstance?}?from?'vue'const?MyComponent?=?{setup()?{const?internalInstance?=?getCurrentInstance()internalInstance.appContext.config.globalProperties?//?訪問?globalProperties}
}

知道這個API后,我們可以在購物車例子的代碼中添加一些代碼。便于我們理解。

//?vuex/examples/composition/shopping-cart/components/App.vue
import?{?getCurrentInstance,?provide?}?from?'vue'
import?{?useStore?}?from?'vuex';
setup?()?{const?store?=?useStore()provide('ruochuan12',?'微信搜索「若川視野」關注我,專注前端技術分享。')window.AppStore?=?store;window.AppCurrentInstance?=?getCurrentInstance();
},
//?vuex/examples/composition/shopping-cart/components/ProductList.vue
setup(){const?store?=?useStore()//?若川加入的調試代碼--startwindow.ProductListStore?=?store;window.ProductListCurrentInstance?=?getCurrentInstance();provide('weixin-2',?'ruochuan12');provide('weixin-3',?'ruochuan12');provide('weixin-4',?'ruochuan12');const?mp?=?inject('ruochuan12');console.log(mp,?'介紹-ProductList');?//?微信搜索「若川視野」關注我,專注前端技術分享。//?若川加入的調試代碼---end
}
//?vuex/examples/composition/shopping-cart/components/ShoppingCart.vue
setup?()?{const?store?=?useStore()//?若川加入的調試代碼--startwindow.ShoppingCartStore?=?store;window.ShoppingCartCurrentInstance?=?getCurrentInstance();provide('weixin',?'ruochuan12');provide('weixin1',?'ruochuan12');provide('weixin2',?'ruochuan12');const?mp?=?inject('ruochuan12');console.log(mp,?'介紹-ShoppingList');?//?微信搜索「若川視野」關注我,專注前端技術分享。//?若川加入的調試代碼--start
}

在控制臺輸出這些值

AppCurrentInstance
AppCurrentInstance.provides
ShoppingCartCurrentInstance.parent?===?AppCurrentInstance?//?true
ShoppingCartCurrentInstance.provides
ShoppingCartStore?===?AppStore?//?true
ProductListStore?===?AppStore?//?true
AppStore?//?store實例對象
控制臺輸出的結果

看控制臺截圖輸出的例子,其實跟上文寫的類似。這時如果寫了順手自己注入了一個provide('store': '空字符串'),那么順著原型鏈,肯定是先找到用戶寫的store,這時Vuex無法正常使用,就報錯了。

當然vuex4提供了注入的key可以不是store的寫法,這時就不和用戶的沖突了。

export?class?Store{//?省略若干代碼...install?(app,?injectKey)?{//?為?composition?API?中使用//??可以傳入?injectKey??如果沒傳取默認的?storeKey?也就是?storeapp.provide(injectKey?||?storeKey,?this)//?為?option?API?中使用app.config.globalProperties.$store?=?this}//?省略若干代碼...
}
export?function?useStore?(key?=?null)?{return?inject(key?!==?null???key?:?storeKey)
}

5. 解答下開頭提出的5個問題

統一解答下開頭提出的5個問題:

1、為什么修改了實例store里的屬性,變更后會觸發視圖更新。

答:使用Vue 中的 reactive 方法監測數據變化的。

class?Store{constructor?(options?=?{}){//?省略若干代碼...this._modules?=?new?ModuleCollection(options)const?state?=?this._modules.root.stateresetStoreState(this,?state)//?省略若干代碼...}
}
function?resetStoreState?(store,?state,?hot)?{//?省略若干代碼...store._state?=?reactive({data:?state})//?省略若干代碼...
}

2、Vuex4作為Vue的插件如何實現和Vue結合的。

答:app.use(store) 時會執行Store中的install方法,一句是為 composition API 中使用,提供Store實例對象到根實例中。一句則是注入到根實例的全局屬性中,為 option API 中使用。它們都會在組件生成時,注入到每個組件實例中。

export?class?Store{//?省略若干代碼...install?(app,?injectKey)?{//?為?composition?API?中使用//??可以傳入?injectKey??如果沒傳取默認的?storeKey?也就是?storeapp.provide(injectKey?||?storeKey,?this)//?為?option?API?中使用app.config.globalProperties.$store?=?this}//?省略若干代碼...
}

3、provideinject的如何實現的,每個組件如何獲取到組件實例中的Store的。

5、為什么在組件中寫的provide提供的數據,能被子級組件獲取到。

答:provide函數建立原型鏈區分出組件實例用戶自己寫的屬性和系統注入的屬性。inject函數則是通過原型鏈找父級實例中的provides對象中的屬性。

//?有刪減
function?provide(){let?provides?=?currentInstance.provides;const?parentProvides?=?currentInstance.parent?&&?currentInstance.parent.provides;if?(parentProvides?===?provides)?{provides?=?currentInstance.provides?=?Object.create(parentProvides);}provides[key]?=?value;
}
//?有刪減
function?inject(){const?provides?=?instance.parent?==?null??instance.vnode.appContext?&&?instance.vnode.appContext.provides:?instance.parent.provides;if?(provides?&&?key?in?provides)?{return?provides[key];}
}

也就是類似這樣的實例:

//?當前組件實例
{parent:?'父級的實例',provides:?{//?可以容納其他屬性,比如用戶自己寫的__proto__:?{//?可以容納其他屬性,比如用戶自己寫的__proto__?:?{?store:?{?__state:?'Store實例'?}??}}}
}

4、為什么每個組件對象里都有Store實例對象了(渲染組件對象過程)。

答:渲染生成組件實例時,調用createComponentInstance,注入到組件實例的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;
}
  1. 你怎么知道那么多的

答:因為社區有人寫了`Vue4`源碼文章[10]

6. 總結

本文主要講述了Vuex4Store實例注入到各個組件中的原理,展開講述了Vuex4相對與Vuex3安裝方式的改變Vuex.createStoreapp.use(store) ,深入源碼分析Vue.injectVue.provide實現原理。

Vuex4 除了安裝方式和監測數據變化方式使用了Vue.reactive,其他基本和Vuex3.x版本沒什么區別。

最后回顧下文章開頭的圖,可以說就是原型鏈的妙用。

provide,inject示例圖

是不是覺得豁然開朗。

Vuex其實也是Vue的一個插件,知曉了Vuex原理,對于自己給Vue寫插件也是會游刃有余。

參考資料

[1]

本文倉庫地址: https://github.com/lxchuan12/vuex4-analysis.git

[2]

更多參考鏈接,可以點擊閱讀原文查看



最近組建了一個江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你進群。


·················?若川出品?·················

今日話題

10篇源碼系列文章小成就達成,從19年7月開始寫,19年寫了6篇,20年寫了2篇,今年寫了2篇。算是一個完結吧。短時間內應該暫時不更新這個系列了。主要是投入的時間和精力比較多,看的人很少,得到的反饋也比較少。之后先寫其他文章吧。歡迎持續關注我(若川)。歡迎在下方留言~? 歡迎分享、收藏、點贊、在看我的公眾號文章~

一個愿景是幫助5年內前端人走向前列的公眾號

可加我個人微信?ruochuan12,長期交流學習

推薦閱讀

我在阿里招前端,我該怎么幫你?(現在還能加我進模擬面試群)

若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?

點擊方卡片關注我、加個星標
學習源碼整體架構系列、年度總結、JS基礎系列

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275848.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275848.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275848.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring4.3x教程之一IOCDI

SpringIOC也稱為DI,對屬性內容的注入可以通過屬性的setXXX方法進行也可以通過構造方法進行,當然還可以使用工廠模式進行屬性內容的注入。 什么是DI?什么是IOC? DI:Dependency Injection依賴注入 其實一個類中的屬性就是…

戰神4 幕后花絮 概念藝術_幕后花絮:品牌更新的背后

戰神4 幕后花絮 概念藝術Under the Hood gives you an inside look at different parts of Waze — straight from the people working on them every day.在引擎蓋下,您可以深入了解Waze的不同部分-直接來自每天進行工作的人員。 Traffic is the worst. It makes …

C#日期控件(js版)

js 腳本代碼: <script type"text/javascript"> //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // 這是一個日歷 Javascript 頁…

python第三周測試_python第三周小測

1.讀取一個文件&#xff0c;顯示除了井號(#)開頭的行意外的所有行# -*- coding: utf-8 -*-"""Created on Tue May 28 09:37:08 2019author: Omega_Sendoh"""#打開文件f open("install-sh","r")#讀取文件的所有行&#xff0…

「Vueconf」探索 Vue3 中 的 JSX

大家好&#xff0c;我是若川。今天再分享 Vueconf 的一篇文章。另外 Vueconf 主辦方提供的錄播鏈接是&#xff1a;?https://www.bilibili.com/read/mobile?id11408693&#xff0c;感興趣可以復制觀看。點擊下方卡片關注我、加個星標。學習源碼整體架構系列、年度總結、JS基礎…

設計模式--享元模式實現C++

/********************************* *設計模式--享元模式實現 *C語言 *Author&#xff1a;WangYong *Blog:http://www.cnblogs.com/newwy ********************************/ #include <iostream> #include <cassert> #include <vector> #include <strin…

安卓加載asset中的json文件_Android解析Asset目錄下的json文件

在app module中的src/main/assets目錄下我們準備了兩個json文件&#xff1a;destination.json如下&#xff1a;{"main/tabs/sofa": {"isFragment": true,"asStarter": false,"needLogin": false,"pageUrl": "main/tabs…

一文搞懂 Promise、Genarator、 Async 三者的區別和聯系

非985/211大學畢業&#xff0c;軟件工程專業&#xff0c;前端&#xff0c;坐標&#xff1a;北京工作三年多&#xff0c;第一家人數 30 多人的創業公司&#xff0c;1 年多。第二家屬于前端技術不錯的公司&#xff0c;2 年多。01我是一個喜歡在技術領域“折騰”的人&#xff0c;技…

閉包,sync使用細節

代碼 先看代碼如下&#xff1a; func main() {var a []intfor i : 0; i < 100; i {go func() {a append(a, i)}()}time.Sleep(2 * time.Second)fmt.Println(a) } 這段測試代碼是想要一個元素為0到100的切片&#xff0c;但是這一小段代碼隱藏了很多的問題。 閉包函數 先看這…

dynamic 儀表板_儀表板完成百萬美元交易

dynamic 儀表板問題 (The Problem) Anybody dealing with tech products and data-focused services runs into the same fundamental problem: what you do is technical but non-technical people control the budget. In other words:任何處理高科技產品和以數據為中心的服務…

checkStyle -- 代碼風格一致

download page: http://sourceforge.net/project/showfiles.php?group_id80344&package_id107587 轉載于:https://www.cnblogs.com/xuqiang/archive/2010/10/26/1953431.html

在線VS Code閱讀源碼神器 github1s

大家好&#xff0c;我是若川。github1s大部分人知道了&#xff0c;但還是有一部分不知道。我在掘金發過沸點和知乎發過想法還是有挺多人不知道&#xff0c;所以再發公眾號推薦下。點擊下方卡片關注我、加個星標。學習源碼整體架構系列、年度總結、JS基礎系列近日&#xff0c;一…

lenze變頻器怎么更改地址_英威騰變頻器GD300維修

英威騰變頻器GD300維修英威騰變頻器GD300維修41. 問題&#xff1a;變頻器跟PLC采用485通訊不上答&#xff1a;1.檢查變頻器的通訊地址是否正確&#xff0c;如果采用通訊啟動&#xff0c;檢查P0.01是否為1&#xff0c;如果通過通訊設定頻率&#xff0c;檢查P0.068&#xff0c;P0…

代碼設計的基礎原則_設計原則:良好設計的基礎

代碼設計的基礎原則As designers, it’s our goal to pass information in the most pleasing way possible. Starting out, there’s a wealth of literature to read and videos to watch that can get quite overwhelming to take in at a glance. People take different ro…

SQL根據細粒度為天的查詢

當我們集成了一些前端框架&#xff0c;在某些展示頁面上往往具有某些查詢條件。而這其中日期查詢的處理又較為麻煩&#xff0c;此處&#xff0c;我羅列了一種當前臺上傳了一種默認的date格式的日期查詢數據至后臺未經Controller或Service層處理直接在SQL中處理的一種方式——即…

企業生產經營相關英文及縮寫之(11)--Genenic 普通書寫

Genenic 普通書寫 ASAP As soon as possible 盡早 BCC Blink Carbon Copy 無信頭抄送&#xff0c;無信頭副本 BR Best Regards 最誠致的問候 BTW By the way 順便問一下 CC Carbon Copy …

java金額類型_Java中存儲金額用什么數據類型?

很早之前, 記得一次面試, 面試官問存儲金錢用什么數據類型? 當時只知道8種數據類型(boolean, byte, short, int, long, float, double, char)的我, 回答了double, 因為我覺得double是雙精度類型, 最適合, 但是面試官告訴我應該用BigDecimal! 最近在做支付的項目, 才對這種數據…

信息技術與信息革命

信息資源管理學什么 圍繞 信息這份戰略資源&#xff0c;從信息資源的管理角度出發&#xff0c;以信息系統為主要研究對象&#xff0c; 研討了信息系統規劃&#xff0c;信息系統開發 信息系統的內容&#xff0c;信息系統安全以及信息資源管理中 涉及的法律法規 知識框架 信息技術…

React Hooks 不知道怎么學?看這篇

大家好&#xff0c;我是若川。最近跟朋友聊技術&#xff0c;發現越來越多的大廠&#xff0c;都優先考慮用 React 做項目&#xff0c;在面試中也經常會考察對 React Hooks 的理解。其實&#xff0c;我一直覺得&#xff0c;React 才是前端的正確打開方式。當然&#xff0c;并不是…

數字與企鵝的戰爭,看周紅衣的高明之處

本文非原創&#xff0c;轉自月光    360與QQ最近的一輪大戰已經接近尾聲&#xff0c;毫無疑問的是&#xff0c;360在這一輪對決中以勝利告終。這場戰爭持續了整整一個月零兩天&#xff0c;先后經歷了幾次小小的高潮&#xff0c;最終周鴻祎祭出絕招&#xff0c;秒殺群雄&#…