[Vue深入組件-邊界情況處理] 訪問元素 & 組件

1. vm.$root

1.1 描述

當前組件樹的根 Vue 實例。如果當前實例沒有父實例,此實例將會是其自己。

在Vue 中, 什麼是一個組件樹的根實例? 實際上,通過Vue Devtools 能夠很清楚的看到:
image-20210906232341442

具體的,在代碼中,Vue 項目的入口文件main.js 中,一般的會有這樣的代碼段:

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

創建了一個Vue實例,並塞入了一些東西后,通過$mount 掛載到了#app 這個dom節點。

實際上,和我們常用的組件化開發一樣,我們也能在創建組件時(即便是根組件),也能夠通過一個個工廠函數的形式,定義一些數據作爲這個組件的動態屬性值。 例如:

new Vue({
  data() {
    return {
      name: "$root",
    };
  },
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

這樣,你便能夠在全局範圍內,在組件樹的任何一個節點,通過vm.$root 獲取到這個根實例,也就能以vm.$root.name 的形式訪問到我們定義的name 屬性。

1.2 能力

我們可以發現,這點很像一箇中央數據管理對象, 類似localStorage,或者更加類似於store,或者Vuex。 沒錯,只是取決於項目的複雜度考慮,通常不會這樣去使用。 但是如果項目本身比較小,而無需引入Vuex 的情況下。 我們可以考慮這樣去實現需求。

並且 ,由於data 對象中的所有的 property 都會被加入到 Vue 的響應式系統中, 這意味着,這個值的變化將會引起所有引用處的dom渲染更新。 即:

<!--組件樹中的任意組件a-->
vm.$root.name = "update name value!"
<!--組件樹中的任意組件b-->
<template>
	{{$root.name}}
</template>

只要在組件樹中,任意組件a中去修改name值,其他任意引用的組件都會自動更新。

2. $parent$children

2.1 描述

$parent 指代父實例,如果當前實例有的話。

$children指代子實例,如果當前實例有的話。

所不同的是,也值得注意的是這二者的類型不同,$children 的類型是 Array<Vue instance> , 而 $parent 的類型是 Vue instance

一個組件可以有多個子組件,因此 $children 的類型是 Array<Vue instance> , 這是易於理解的,但是,爲什麼 $parent 的類型是 Vue instance 而不是 Array<Vue instance> 呢?

一個組件不是也可以被多個組件引用嗎? 那父組件不是也應該有多個嗎?,如果我們畫一個圖來理解:

image-20210907001711604

其實,這個原因,文檔中早有提及:

注意當點擊按鈕時,每個組件都會各自獨立維護它的 count。因爲你每用一次組件,就會有一個它的新實例被創建。link

組件被複用時,爲了不會相互影響,Vue 實際上在背後爲我們創建了一個全新的實例。

以下是一個驗證demo:

image-20210907004800746

2.2 能力

通過這兩個屬性,我們能夠在兩個嵌套關係的組件中,輕鬆的從子組件訪問父組件實例,或者從父組件訪問子組件實例。

3. 通過$ref 訪問子組件實例或者元素

3.1描述:

通過ref 給一個子組件標記一個"id", 然後當子組件掛載後,便可以通過$ref.refname 的形式訪問到對應的子組件實例。

在項目開發中,這是一個常使用的子組件實例訪問方式。

以下是一個使用示例。

image-20210908000234163

3.2 能力

通過$refs ,vue又給了我們一種訪問組件實例的能力,在一定場景下靈活運用,能極大的方便我們的開發過程。

3.3 注意點

  1. 要注意使用的所在生命週期

    在使用ref這種方式時,你特別需要注意的是,你僅能在該組件掛載完畢後才能訪問到該組件實例,所有在使用時,你需要確保組件已經掛載的情況下,再使用。

    一般來說,父子組件的createdmounted 聲明週期通常是這樣的:
    父組件 created --> 子組件 created --> 子組件 mounted --> 父組件mounted

    這意味着,如果你不加以注意的在父組件的created 聲明週期鉤子種去通過ref 訪問子組件實例,是不會成功的:

    image-20210908001206560

    如果你嘗試通過ref 訪問一個根元素上有v-if 的組件元素,通常是會遇到一些問題的。 因爲一些場景下,v-if 的接入會導致上面提到的常規父子生命週期鉤子的執行順序失常。 爲了解決該問題,通常你可以利用$nextTick

  2. 不要依賴ref實現響應式

    $refs 只會在組件渲染完成之後生效,並且它們不是響應式的。這僅作爲一個用於直接操作子組件的“逃生艙”——你應該避免在模板或計算屬性中訪問 $refs

    例如,這樣是不能預期運行的:

    image-20210908002756585

4. 依賴注入

4.1 描述

假如,有一個多級嵌套的組件,而你需要在最內部去調用最外層組件的方法,你會怎麼辦? 先不考慮這樣的組件是否合適,也不考慮更好的抽離方法。

那麼,首先容易想到的是,我們首先要獲取到這個最外層的組件的實例,才能去調用最外層組件中methods 對象中定義的方法。

前面我們剛提到過$parent可以獲取到父組件的實例。 那麼如果層級比較淺還好,我們通過 this.$parent.$parent.....xxxfunc() 這樣的"鏈式訪問"就能達到目的了。

但是顯然,這是不利於維護的。 也是不太聰明的。

因爲一旦你中間需要多嵌套一級組件,那麼你就需要對剛纔的”鏈式訪問加一個$parent“。

爲了說的更加清楚,我們寫一個簡單的demo復現一下這種場景:
image-20210908010213289

簡單說明一下,在index.vue中,我們引入了LevelTwoLevelThree 兩個組件,並且寫作了嵌套的形式。

之所以能寫作嵌套的形式,是利用了<slot> ,如果你對這裏不太清楚,請回顧前面的插槽內容。這裏只提一點:
如果LevelTwo 組件定義中,沒有包含一個 <slot> 元素,則該組件<LevelTwo>起始標籤和</LevelTwo>結束標籤之間的任何內容都會被拋棄。

這樣,就構成了一個三級嵌套的組件層級關係。即:

<index>
	└── <LevelTwo>
   			 └── <LevelThree>

那麼,我們在<LevelTwo> 中去調用時,就是這樣去調用的:

this.$parent.indexFunc();

同理,在<LevelThree>,是這樣調用的:

this.$parent.$parent.indexFunc();

如果,嵌套層級更深會很不方便,且一旦有層級上的改動,這裏都要動。

4.2 通過依賴注入,解決特定場景下的問題

首先,這種場景並不常見。 因爲一般有公共方法,我們第一想法應該是把它抽離出去。然後在需要使用的地方去引入。那麼這裏vue,也爲這種特定場景,提供瞭解決方案,那就是 依賴注入。

其實,易於理解,我們把子組件中可能用到的方法,通過某種方式給他"註冊"一下。

然後在需要用到該方法的子組件中,去"引入"一下。

我們直接通過示例認識一下用法:

image-20210908010446034

【簡單描述】:

Vue 爲我們提供兩個新的實例選項:provideinject

provide 選項允許我們指定我們想要提供給後代組件的數據/方法。也就是我們剛纔提到的"註冊"

provide: function() {
    return {
        indexFunc: this.indexFunc,
    };
},

然後在需要用到的所有子組件中,通過inject "注入" provide 的數據/方法, 也就是我們剛纔提到的"引入"

最後,直接通過this.indexFunc() 就可以成功調用了。

這就是依賴注入。

vue 文檔上用的示例是google map , 對國內用戶確實不太友好,因爲假如你想寫個google map 的demo , 你還得搞到google map 開發的key纔行 - -

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章