<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>

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

          共 35043字,需瀏覽 71分鐘

           ·

          2021-05-27 13:57

          1. 前言

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

          本文倉(cāng)庫(kù)地址[1]git clone https://github.com/lxchuan12/vuex4-analysis.git,本文最佳閱讀方式,克隆倉(cāng)庫(kù)自己動(dòng)手調(diào)試,容易吸收消化。

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

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

          閱讀本文后你將學(xué)到:

            git subtree 管理子倉(cāng)庫(kù)
            如何學(xué)習(xí)Vuex 4源碼、理解Vuex原理
            Vuex 4Vuex 3 的異同
            Vuex 4 composition API 如何使用
            Vue.provide / Vue.inject API 使用和原理
            如何寫一個(gè) Vue3 插件
            等等

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

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

          之前寫過Vuex 3的源碼文章學(xué)習(xí) vuex 源碼整體架構(gòu),打造屬于自己的狀態(tài)管理庫(kù)[2],倉(cāng)庫(kù)有很詳細(xì)的注釋和看源碼方法,所以本文不會(huì)過多贅述與Vuex 3源碼相同的地方。

          1.1 本文閱讀最佳方式

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

          2. Vuex 原理簡(jiǎn)述

          結(jié)論先行Vuex原理可以拆解為三個(gè)關(guān)鍵點(diǎn)。第一點(diǎn)、其實(shí)就是每個(gè)組件實(shí)例里都注入了Store實(shí)例。第二點(diǎn)、Store實(shí)例中的各種方法都是為Store中的屬性服務(wù)的。第三點(diǎn)、Store中的屬性變更觸發(fā)視圖更新。

          本文主要講解第一點(diǎn)。第二點(diǎn)在我的上一篇文章學(xué)習(xí) vuex 源碼整體架構(gòu),打造屬于自己的狀態(tài)管理庫(kù)詳細(xì)講了,本文就不贅述了。第三點(diǎn)兩篇文章都沒有詳細(xì)講述。

          以下是一段簡(jiǎn)短的代碼說明Vuex原理的。

          // 簡(jiǎn)版
          class Store{
            constructor(){
              this._state = 'Store 實(shí)例';
            }
            dispatch(val){
              this.__state = val;
            }
            commit(){}
            // 省略
          }


          const store = new Store();
          var rootInstance = {
            parentnull,
            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 這些對(duì)象中的provides.store都改了。
          // 因?yàn)楣蚕碇粋€(gè)store對(duì)象。
          provide,inject示例圖

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

          那么接下來,帶著問題:

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

          3. Vuex 4 重大改變

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

          從 3.x 遷移到 4.0[5]

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

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

          3.1 安裝過程

          Vuex 3Vue.use(Vuex)

          Vuex 4則是app.use(store)

          import { createStore } from 'vuex'

          export const store = createStore({
            state() {
              return {
                count1
              }
            }
          })
          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 核心模塊導(dǎo)出了 createLogger 函數(shù)

          import { createLogger } from 'vuex'

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

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

          4.1 chrome 調(diào)試 Vuex 4 源碼準(zhǔn)備工作

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

          這種方式保留了vuex4倉(cāng)庫(kù)的git記錄信息。更多git subtree使用方式可以查看這篇文章用 Git Subtree 在多個(gè) Git 項(xiàng)目間雙向同步子項(xiàng)目,附簡(jiǎn)明使用手冊(cè)[6]。

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

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

          我們使用項(xiàng)目中的購(gòu)物車的例子調(diào)試,貫穿全文。

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

          據(jù)說一圖勝千言,這時(shí)簡(jiǎn)單截個(gè)調(diào)試的圖。

          vuex debugger

          找到 createStore函數(shù)打上斷點(diǎn)。

          // 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')等打上斷點(diǎn)。

          // 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)兩個(gè)方面發(fā)散開來講解。

          4.2 Vuex.createStore 函數(shù)

          相比 Vuex 3 中,new Vuex.Store,其實(shí)是一樣的。只不過為了和Vue 3 統(tǒng)一,Vuex 4 額外多了一個(gè) createStore 函數(shù)。

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

          監(jiān)測(cè)數(shù)據(jù)

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

          Vue.reactive 函數(shù)方法,本文就不展開講解了。因?yàn)檎归_來講,又可以寫篇新的文章了。只需要知道主要功能是監(jiān)測(cè)數(shù)據(jù)改變,變更視圖即可。

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

          跟著斷點(diǎn)我們繼續(xù)看app.use()方法,Vue提供的插件機(jī)制。

          4.3 app.use() 方法

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

          執(zhí)行插件,如果是對(duì)象,install是函數(shù),則把參數(shù)app和其他參數(shù)傳遞給install函數(shù)執(zhí)行。如果是函數(shù)直接執(zhí)行。

          // 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) {
                    // 已經(jīng)有插件,并且 不是生產(chǎn)環(huán)境,報(bào)警告。
                      if (installedPlugins.has(plugin)) {
                          (process.env.NODE_ENV !== 'production') && warn(`Plugin has already been applied to target app.`);
                      }
                      // 插件的install 是函數(shù),則添加插件,并執(zhí)行 install 函數(shù)
                      else if (plugin && isFunction(plugin.install)) {
                          installedPlugins.add(plugin);
                          // 斷點(diǎn)
                          plugin.install(app, ...options);
                      }
                      // 插件本身 是函數(shù),則添加插件,并執(zhí)行 插件本身函數(shù)
                      else if (isFunction(plugin)) {
                          installedPlugins.add(plugin);
                          plugin(app, ...options);
                      }
                      // 如果都不是報(bào)警告
                      else if ((process.env.NODE_ENV !== 'production')) {
                          warn(`A plugin must either be a function or an object with an "install" ` +
                              `function.`);
                      }
                      // 支持鏈?zhǔn)秸{(diào)用
                      return app;
                  },
                  provide(){ 
                    // 省略... 后文再講
                  }
                });
              }
          }

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

          跟著斷點(diǎn)走到下一步,install函數(shù)。

          4.4 install 函數(shù)

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

          Vuex4中的install函數(shù)相對(duì)比Vuex3中簡(jiǎn)單了許多。第一句是給Composition API提供的。注入到根實(shí)例對(duì)象中。第二句則是為option API提供的。

          接著斷點(diǎn)這兩句,按F11來看app.provide實(shí)現(xiàn)。

          4.4.1 app.provide

          簡(jiǎn)單來說就是給contextprovides屬性中加了store = Store實(shí)例對(duì)象

          provide(key, value) {
              // 如果已經(jīng)有值了警告
              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/24587
              context.provides[key] = value;
              return app;
          }

          接著從上方代碼中搜索context,可以發(fā)現(xiàn)這一句代碼:

          const context = createAppContext();

          接著我們來看函數(shù) createAppContextcontext 為上下文

          // webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
          function createAppContext({
              return {
                  appnull,
                  config: {
                      isNativeTag: NO,
                      performancefalse,
                      globalProperties: {},
                      optionMergeStrategies: {},
                      isCustomElement: NO,
                      errorHandlerundefined,
                      warnHandlerundefined
                  },
                  mixins: [],
                  components: {},
                  directives: {},
                  providesObject.create(null)
              };
          }

          Vue3 文檔應(yīng)用配置(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) // '{}'
            }
          })

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

          也就是說在appContext.provides中注入了一個(gè)Store實(shí)例對(duì)象。這時(shí)也就是相當(dāng)于根組件實(shí)例和config全局配置globalProperties中有了Store實(shí)例對(duì)象。

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

          app.provide 其實(shí)是用于composition API使用的。

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

          接下來,我們看下源碼具體實(shí)現(xiàn),為什么每個(gè)組件實(shí)例中都能獲取到的。

          這之前先來看下組合式API中,我們?nèi)绾问褂?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(60, 112, 198);">Vuex4,這是線索。

          4.5 composition API 中如何使用Vuex 4

          接著我們找到如下文件,useStore是我們斷點(diǎn)的對(duì)象。

          // 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;
              // 省略了若干代碼
            }
          }

          接著斷點(diǎn)按F11,單步調(diào)試,會(huì)發(fā)現(xiàn)最終是使用了Vue.inject方法。

          4.5.1 Vuex.useStore 源碼實(shí)現(xiàn)

          // 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 源碼實(shí)現(xiàn)

          接著看inject函數(shù),看著代碼很多,其實(shí)原理很簡(jiǎn)單,就是要找到我們用provide提供的值。

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

          Vuex4源碼里則是:Store實(shí)例對(duì)象。

          // 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
              // 如果是被一個(gè)函數(shù)式組件調(diào)用則取 currentRenderingInstance
              const instance = currentInstance || currentRenderingInstance;
              if (instance) {
                  // #2400
                  // to support `app.use` plugins,
                  // fallback to appContext's `provides` if the intance is at root
                  const 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 type
                      return provides[key];
                  }
                  // 如果參數(shù)大于1個(gè) 第二個(gè)則是默認(rèn)值 ,第三個(gè)參數(shù)是 true,并且第二個(gè)值是函數(shù)則執(zhí)行函數(shù)。
                  else if (arguments.length > 1) {
                      return treatDefaultAsFactory && isFunction(defaultValue)
                          ? defaultValue()
                          : defaultValue;
                  }
                  // 警告沒找到
                  else if ((process.env.NODE_ENV !== 'production')) {
                      warn(`injection "${String(key)}" not found.`);
                  }
              }
              // 如果沒有當(dāng)前實(shí)例則說明則報(bào)警告。
              // 也就是是說inject必須在setup中調(diào)用或者在函數(shù)式組件中使用
              else if ((process.env.NODE_ENV !== 'production')) {
                  warn(`inject() can only be used inside setup() or functional components.`);
              }
          }

          接著我們繼續(xù)來看inject的相對(duì)應(yīng)的provide。

          4.5.3  Vue.provide 源碼實(shí)現(xiàn)

          provide函數(shù)作用其實(shí)也算簡(jiǎn)單,1、也就是給當(dāng)前組件實(shí)例上的provides對(duì)象屬性,添加鍵值對(duì)key/value

          2、還有一個(gè)作用是當(dāng)當(dāng)前組件和父級(jí)組件的provides相同時(shí),在當(dāng)前組件實(shí)例中的provides對(duì)象和父級(jí),則建立鏈接,也就是原型[[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 type
                  provides[key] = value;
              }
          }

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

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

          我們來舉個(gè)例子消化一下。

          var currentInstance = { provides: { store: { __state'Store實(shí)例' }  } };
          var provides = currentInstance.provides;
          // 這句是我手動(dòng)加的,在后文中則是創(chuàng)建實(shí)例時(shí)就是寫的同一個(gè)對(duì)象,當(dāng)然就會(huì)相等了。
          var parentProvides = provides;
          if(parentProvides === provides){
              provides =  currentInstance.provides = Object.create(parentProvides);
          }

          經(jīng)過一次執(zhí)行這個(gè)后,currentInstance 就變成了這樣。

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

          執(zhí)行第二次時(shí),currentInstance 則是:

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

          以此類推,多執(zhí)行provide幾次,原型鏈就越長(zhǎng)。

          上文inject、provide函數(shù)中都有個(gè)變量currentInstance當(dāng)前實(shí)例,那么當(dāng)前實(shí)例對(duì)象是怎么來的呢。

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

          接下來我們createComponentInstance函數(shù)如何創(chuàng)建組件實(shí)例。

          4.6 createComponentInstance 創(chuàng)建組件實(shí)例

          可以禁用其他斷點(diǎn),單獨(dú)斷點(diǎn)這里, 比如:const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;來看具體實(shí)現(xiàn)。

          // 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,
                  rootnull,
                  nextnull,
                  subTreenull,
                  // ...
                  provides: parent ? parent.provides : Object.create(appContext.provides),
                  // ...
              }
              instance.root = parent ? parent.root : instance;
              // ...
              return instance;
          }

          斷點(diǎn)時(shí)會(huì)發(fā)現(xiàn),根組件實(shí)例時(shí)vnode已經(jīng)生成,至于是什么時(shí)候生成的,我整理了下簡(jiǎn)化版。

          // 把上文中的 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 其實(shí)就是建立原型關(guān)系。這時(shí)放一張圖,一圖勝千言。

          直觀的圖

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

          4.6.1 組件實(shí)例生成了,那怎么把它們結(jié)合呢

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

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

          // 當(dāng)前組件實(shí)例
          {
            parent'父級(jí)的實(shí)例',
            provides: {
              // 可以容納其他屬性,比如用戶自己寫的
              __proto__: {
                  // 可以容納其他屬性,比如用戶自己寫的
                  __proto__ : { store: { __state'Store實(shí)例' }  }
              }
            }
          }
          // 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 可以是對(duì)象或者是函數(shù)
                    const provides = isFunction(provideOptions)
                        ? provideOptions.call(publicThis)
                        : provideOptions;
                    Reflect.ownKeys(provides).forEach(key => {
                        provide(key, provides[key]);
                    });
                });
            }
            // ...
          }

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

          接著我們跟著項(xiàng)目來驗(yàn)證下,上文中的表述。翻看Vue3文檔可以發(fā)現(xiàn)有一個(gè)API可以獲取當(dāng)前組件實(shí)例。

          4.7 getCurrentInstance 獲取當(dāng)前實(shí)例對(duì)象

          getCurrentInstance 支持訪問內(nèi)部組件實(shí)例,用于高階用法或庫(kù)的開發(fā)。

          import { getCurrentInstance } from 'vue'

          const MyComponent = {
            setup() {
              const internalInstance = getCurrentInstance()

              internalInstance.appContext.config.globalProperties // 訪問 globalProperties
            }
          }

          知道這個(gè)API后,我們可以在購(gòu)物車?yán)拥拇a中添加一些代碼。便于我們理解。

          // vuex/examples/composition/shopping-cart/components/App.vue
          import { getCurrentInstance, provide } from 'vue'
          import { useStore } from 'vuex';
          setup () {
            const store = useStore()
            provide('ruochuan12''微信搜索「若川視野」關(guān)注我,專注前端技術(shù)分享。')

            window.AppStore = store;
            window.AppCurrentInstance = getCurrentInstance();
          },
          // vuex/examples/composition/shopping-cart/components/ProductList.vue
          setup(){
            const store = useStore()

            // 若川加入的調(diào)試代碼--start
            window.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'); // 微信搜索「若川視野」關(guān)注我,專注前端技術(shù)分享。
            // 若川加入的調(diào)試代碼---end
          }
          // vuex/examples/composition/shopping-cart/components/ShoppingCart.vue
          setup () {
              const store = useStore()

              // 若川加入的調(diào)試代碼--start
              window.ShoppingCartStore = store;
              window.ShoppingCartCurrentInstance = getCurrentInstance();
              provide('weixin''ruochuan12');
              provide('weixin1''ruochuan12');
              provide('weixin2''ruochuan12');
              const mp = inject('ruochuan12');
              console.log(mp, '介紹-ShoppingList'); // 微信搜索「若川視野」關(guān)注我,專注前端技術(shù)分享。
              // 若川加入的調(diào)試代碼--start
          }

          在控制臺(tái)輸出這些值

          AppCurrentInstance
          AppCurrentInstance.provides
          ShoppingCartCurrentInstance.parent === AppCurrentInstance // true
          ShoppingCartCurrentInstance.provides
          ShoppingCartStore === AppStore // true
          ProductListStore === AppStore // true
          AppStore // store實(shí)例對(duì)象
          控制臺(tái)輸出的結(jié)果

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

          當(dāng)然vuex4提供了注入的key可以不是store的寫法,這時(shí)就不和用戶的沖突了。

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

          5. 解答下開頭提出的5個(gè)問題

          統(tǒng)一解答下開頭提出的5個(gè)問題:

          1、為什么修改了實(shí)例store里的屬性,變更后會(huì)觸發(fā)視圖更新。

          答:使用Vue 中的 reactive 方法監(jiān)測(cè)數(shù)據(jù)變化的。

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

          2、Vuex4作為Vue的插件如何實(shí)現(xiàn)和Vue結(jié)合的。

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

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

          3、provide、inject的如何實(shí)現(xiàn)的,每個(gè)組件如何獲取到組件實(shí)例中的Store的。

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

          答:provide函數(shù)建立原型鏈區(qū)分出組件實(shí)例用戶自己寫的屬性和系統(tǒng)注入的屬性。inject函數(shù)則是通過原型鏈找父級(jí)實(shí)例中的provides對(duì)象中的屬性。

          // 有刪減
          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];
              }
          }

          也就是類似這樣的實(shí)例:

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

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

          答:渲染生成組件實(shí)例時(shí),調(diào)用createComponentInstance,注入到組件實(shí)例的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. 你怎么知道那么多的

          答:因?yàn)樯鐓^(qū)有人寫了`Vue4`源碼文章[10]。

          6. 總結(jié)

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

          Vuex4 除了安裝方式和監(jiān)測(cè)數(shù)據(jù)變化方式使用了Vue.reactive,其他基本和Vuex3.x版本沒什么區(qū)別。

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

          provide,inject示例圖

          是不是覺得豁然開朗。

          Vuex其實(shí)也是Vue的一個(gè)插件,知曉了Vuex原理,對(duì)于自己給Vue寫插件也是會(huì)游刃有余。

          參考資料

          [1]

          本文倉(cāng)庫(kù)地址: https://github.com/lxchuan12/vuex4-analysis.git

          [2]

          更多參考鏈接,可以點(diǎn)擊閱讀原文查看




          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
          2. 歡迎加我微信「TH0000666」一起交流學(xué)習(xí)...
          3. 關(guān)注公眾號(hào)「前端Sharing」,持續(xù)為你推送精選好文。

          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亲子伦视频一区二区三区 | 色综合八区 | 黄色网址免费进入 | I片毛片| 天天爽天天干成人av一区二区三区 |