三. Vue組件化

1. 認識組件化

1.1 什麼是組件化

人面對複雜問題的處理方式vue

任何一我的處理信息的邏輯能力都是有限的,因此當面對一個很是複雜的問題時咱們不太可能一次性搞定一大堆的內容。vuex

可是咱們人有一種天生的能力就是將問題進行拆解。若是將一個複雜的問題拆分紅不少個能夠處理的小問題再將其放在總體當中,你會發現大的問題也會迎刃而解。數組

組件化也是相似的思想瀏覽器

若是咱們將一個頁面中全部的處理邏輯所有放在一塊兒,處理起來就會變得很是複雜,並且不利於後續的管理以及擴展。服務器

但若是咱們將一個頁面拆分紅一個個小的功能塊,每一個功能塊完成屬於本身這部分獨立的功能,那麼以後整個頁面的管理和維護就變得很是容易了。網絡

image-20201124214310726
1.2 Vue組件化思想

組件化是Vue.js中的重要思想函數

它提供了一種抽象,讓咱們能夠開發出一個個獨立可複用的小組件來構造咱們的應用,任何的應用都會被抽象成一顆組件樹組件化

image-20201124214641019

組件化思想的應用學習

有了組件化的思想,咱們在以後的開發中就要充分的利用它測試

儘量的將頁面拆分紅一個個小的、可複用的組件

這樣讓咱們的代碼更加方便組織和管理,而且擴展性也更強

2. 註冊組件

2.1 註冊組件的基本步驟

組件的使用分紅三個步驟

  • 建立組件構造器

  • 註冊組件

  • 使用組件

image-20201124215013411

三個步驟的含義

  • Vue.extend()

    • 調用 Vue.extend() 建立的是一個組件構造器
    • 一般在建立組件構造器時,傳入template表明咱們自定義組件的模板
    • 該模板就是在使用到組件的地方要顯示的HTML代碼
    • 事實上,這種寫法在 Vue2.x 的文檔中幾乎已經看不到了,它會直接使用下面如 2.4 形式的語法糖,可是在不少資料仍是會提到這種方式,並且這種方式是學習後面方式的基礎
  • Vue.component()

    • 調用 Vue.component() 是將剛纔的組件構造器註冊爲一個組件而且給它起一個組件的標籤名稱
    • 因此須要傳遞兩個參數:① 註冊組件的標籤名 ② 組件構造器
  • 在Vue實例的做用範圍內使用組件

    • 組件必須掛載在某個Vue實例下,不然它不會生效
    • 下面我使用了三次<my-cpn></my-cpn> ,而第三次其實並無生效
image-20201124215628434
2.2 組件的做用域

全局組件

當咱們經過調用 Vue.component() 註冊組件時,組件的註冊是全局的這意味着該組件能夠在任意Vue示例下使用

局部組件

若是咱們註冊的組件是掛載在某個實例中, 那麼就是一個局部組件

image-20201124221306083
2.3 父子組件

在前面咱們看到了組件樹

組件和組件之間存在層級關係

而其中一種很是重要的關係就是父子組件的關係

咱們來看經過代碼如何組成的這種層級關係

image-20201124221953846

父子組件錯誤用法:以子標籤的形式在Vue實例中使用

由於當子組件註冊到父組件的components時,Vue會編譯好父組件的模塊

該模板的內容已經決定了父組件將要渲染的HTML(至關於父組件中已經有了子組件中的內容了

<child-cpn></child-cpn> 是隻能在父組件中被識別的

相似這種用法,<child-cpn></child-cpn>是會被瀏覽器忽略的

2.4 註冊組件語法糖

在上面註冊組件的方式,可能會有些繁瑣

Vue爲了簡化這個過程,提供了註冊的語法糖

主要是省去了調用 Vue.extend() 的步驟,而是能夠直接使用一個對象來代替

語法糖註冊全局組件和局部組件以下

image-20201124222436062

3. 組件其餘補充

3.1 模板的分離寫法

剛纔咱們經過語法糖簡化了Vue組件的註冊過程,另外還有一個地方的寫法比較麻煩,就是template模塊寫法

若是咱們能將其中的HTML分離出來寫,而後掛載到對應的組件上,必然結構會變得很是清晰

Vue提供了兩種方案來定義HTML模塊內容:

  • 使用 <script> 標籤
  • 使用 <template> 標籤
image-20201124222815433
3.2 組件能夠Vue的實例數據嗎?

組件是一個單獨功能模塊的封裝

這個模塊有屬於本身的HTML模板,也應該有屬性本身的數據data

組件中的數據是保存在哪裏呢?頂層的Vue實例中嗎?

  • 以下測試發現不能並不能訪問,並且即便能夠訪問,若是將全部的數據都放在Vue實例中Vue實例就會變的很是臃腫
  • 結論:組件並不能直接訪問Vue實例中的data,Vue組件應該有本身保存數據的地方
image-20201124223920656
3.3 組件數據的存放

組件本身的數據存放在哪裏呢?

組件對象也有一個data屬性(固然也能夠有methods等屬性,下面咱們有用到)

只是這個data屬性必須是一個函數

並且這個函數返回一個對象,對象內部保存着數據

image-20201124223817597

爲何data在組件中必須是一個函數呢?

首先,若是不是一個函數Vue直接就會報錯

其次,緣由是在於Vue讓每一個組件對象都返回一個新的對象。由於若是是同一個對象的,組件在屢次使用後會相互影響

image-20201124224212074

4. 父子組件通訊

4.1 父子組件通訊理解

以前咱們提到了子組件是不能引用父組件或者Vue實例的數據的

可是,在開發中每每一些數據確實須要從上層傳遞到下層:

  • 好比在一個頁面中咱們從服務器請求到了不少的數據

  • 其中一部分數據並不是是咱們整個頁面的大組件來展現的,而是須要下面的子組件進行展現

  • 這個時候並不會讓子組件再次發送一個網絡請求,而是直接讓 大組件(父組件) 將數據傳遞給 小組件(子組件)

  • 如何進行父子組件間的通訊呢?Vue官方提到

    • 經過 props 向子組件傳遞數據
    • 經過 事件 向父組件發送消息
image-20201124225224780

在下面的代碼中,我直接將Vue實例當作父組件而且其中包含子組件來簡化代碼

真實的開發中,Vue實例和子組件的通訊和父組件和子組件的通訊過程是同樣的

4.2 父組件向子組件傳遞數據 - props

基本用法

在組件中,使用選項props來聲明須要從父級接收到的數據

props的值有兩種方式

  • 字符串數組,數組中的字符串就是傳遞時的名稱
  • 對象,對象能夠設置傳遞時的類型,也能夠設置默認值等

咱們先來看一個最簡單的props傳遞

image-20201124225501375

props數據驗證

在前面咱們的props選項是使用一個數組

除了數組以外咱們也可使用對象,當須要對props進行類型等驗證時就須要對象寫法了

驗證支持以下數據類型

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
image-20201124225932450

當咱們有自定義構造函數時,驗證也支持自定義的類型

image-20201124225823411
4.4 子組件向父組件傳遞數據或事件 - $emit()

自定義事件

props用於父組件向子組件傳遞數據,還有一種比較常見的是子組件傳遞數據或事件到父組件中咱們應該如何處理呢?這個時候咱們須要使用自定義事件來完成

何時須要自定義事件?

當子組件須要向父組件傳遞數據時,就要用到自定義事件了

咱們以前學習的v-on不只僅能夠用於監聽DOM事件,也能夠用於組件間的自定義事件

自定義事件的流程

在子組件中,經過$emit()來觸發事件

在父組件中,經過v-on來監聽子組件事件

咱們來看一個簡單的例子:

  • 咱們以前作過一個兩個按鈕 +1-1 ,點擊後修改 counter
  • 咱們整個操做的過程仍是在子組件中完成,可是以後的展現交給父組件
  • 這樣咱們就須要將子組件中的 counter,傳給父組件的某個屬性好比 total
image-20201124230536738
4.5 父子組件的直接訪問方式 - $children或$refs  /  $parent

理解

有時候咱們須要父組件直接訪問子組件,子組件直接訪問父組件,或者是子組件訪問根組件

  • 父組件訪問子組件:使用$children$refs

  • 子組件訪問父組件:使用$parent

$children

this.$children是一個數組類型,它包含全部子組件對象

咱們這裏經過一個遍歷,取出全部子組件的message狀態

image-20201124231839032

$refs

$children的缺陷

經過 $children 訪問子組件時,是一個數組類型,訪問其中的子組件必須經過索引值

可是當子組件過多,咱們須要拿到其中一個時每每不能肯定它的索引值,甚至還可能會發生變化

有時候咱們想明確獲取其中一個特定的組件,這個時候就可使用$refs

$refs的使用

$refsref指令 一般是一塊兒使用的

首先咱們經過 ref 給某一個子組件綁定一個特定的ID

其次經過 this.$refs.ID 就能夠訪問到該組件了

image-20201124232844530

$parent

若是咱們想在子組件中直接訪問父組件,能夠經過$parent

注意

儘管在Vue開發中,咱們容許經過$parent來訪問父組件,可是在真實開發中儘可能不要這樣作

子組件應該儘可能避免直接訪問父組件的數據,由於這樣耦合度過高了
若是咱們將子組件放在另一個組件以內,極可能該父組件沒有對應的屬性,每每會引發問題

另外更很差作的是經過$parent直接修改父組件的狀態,那麼父組件中的狀態將變得飄忽不定,很不利於個人調試和維護

image-20201124233250766

5. 非父子組件通訊

5.1 理解

剛纔咱們討論的都是父子組件間的通訊,那若是是非父子關係呢?

非父子組件關係包括多個層級的組件,也包括兄弟組件的關係

Vue1.x的時候,能夠經過 $dispatch$broadcast完成,可是在 Vue2.x都被取消了

  • $dispatch用於向上級派發事件

  • $broadcast用於向下級廣播事件

Vue2.x 中,有一種方案是經過中央事件總線,也就是一箇中介來完成

  • 可是這種方案和直接使用 Vuex 的狀態管理方案仍是遜色不少
  • 而且 Vuex 提供了更多好用的功能,因此這裏咱們暫且不討論這種方案,後續咱們專門學習 Vuex 的狀態管理
5.2 中央事件總線
5.3 Vuex狀態管理(後面專門講)

6. 插槽slot

6.1 編譯做用域

在真正學習插槽以前咱們須要先理解一個概念:編譯做用域

官方對於編譯的做用域解析比較簡單,咱們本身來經過一個例子來理解這個概念

咱們來考慮下面的代碼是否最終是能夠渲染出來的:

  • <my-cpn v-show="isShow"></my-cpn>中,咱們使用了 isShow屬性

  • isShow屬性包含在組件中,也包含在Vue實例中

答案:最終能夠渲染出來,也就是使用的是Vue實例的屬性。爲何呢?

  • 官方給出了一條準則:父組件模板的全部東西都會在父級做用域內編譯;子組件模板的全部東西都會在子級做用域內編譯
  • 而咱們在使用 <my-cpn v-show="isShow"></my-cpn> 的時候,整個組件的使用過程是至關於在父組件中出現的
  • 那麼他的做用域就是父組件,使用的屬性也是屬於父組件的屬性
  • 所以 isShow使用的是Vue實例中的屬性,而不是子組件的屬性
image-20201124234313056
6.2 爲何使用slot

slot翻譯爲插槽

在生活中不少地方都有插槽,電腦的USB插槽,插板當中的電源插槽

插槽的目的是讓咱們原來的設備具有更多的擴展性

好比電腦的USB咱們能夠插入U盤、硬盤、手機、音響、鍵盤、鼠標等

組件的插槽

組件的插槽也是爲了讓咱們封裝的組件更加具備擴展性

讓使用者能夠決定組件內部的一些內容到底展現什麼

例子:移動網站中的導航欄

  • 移動開發中,幾乎每一個頁面都有導航欄

  • 導航欄咱們必然會封裝成一個插件,好比nav-bar組件

  • 一旦有了這個組件,咱們就能夠在多個頁面中複用了

  • 可是,每一個頁面的導航是同樣的嗎?No,我以京東M站爲例

image-20201124234612254
6.3 如何在封裝組件時正確使用slot

如何去封裝京東M站導航欄這類的組件呢?

它們也不少區別,可是也有不少共性

若是咱們每個單獨去封裝一個組件顯然不合適:好比每一個頁面都返回,這部份內容咱們就要重複去封裝

可是若是咱們封裝成一個好像也不合理:有些左側是菜單,有些是返回,有些中間是搜索,有些是文字等

如何封裝合適呢?抽取共性,保留不一樣

  • 最好的封裝方式就是將共性抽取到組件中,將不一樣暴露爲插槽

  • 一旦咱們預留了插槽,就可讓使用者根據本身的需求,決定插槽中插入什麼內容

  • 是搜索框,仍是文字,仍是菜單。由調用者本身來決定

  • 這就是爲何咱們要學習組件中的插槽slot的緣由

6.4 slot基本使用

瞭解了爲何用slot,咱們再來談談如何使用slot?

  • 在子組件中,使用特殊的元素 <slot> 就能夠爲子組件開啓一個插槽。

  • 該插槽插入什麼內容取決於父組件如何使用。

咱們經過一個簡單的例子,來給子組件定義一個插槽

  • <slot> 中的內容表示,若是沒有在該組件中插入任何其餘內容,就默認顯示該內容
  • 有了這個插槽後,父組件如何使用呢?
image-20201128135942587
6.5 具名插槽slot

當子組件的功能複雜時,子組件的插槽可能並不是是一個

好比咱們封裝一個導航欄的子組件,可能就須要三個插槽,分別表明左邊、中間、右邊。、

那麼,外面在給插槽插入內容時,如何區分插入的是哪個呢?

這個時候,咱們就須要給插槽起一個名字

image-20201128135942587

如何使用具名插槽呢?

很是簡單,只要給slot元素一個name屬性便可

<slot name='myslot'></slot>

咱們來給出一個案例:

這裏咱們先不對導航組件作很是複雜的封裝,先了解具名插槽的用法。

image-20201128140254483
6.6 做用域插槽

做用域插槽是slot一個比較難理解的點,並且官方文檔說的又有點不清晰。

這裏,咱們用一句話對其作一個總結,而後咱們在後續的案例中來體會:父組件替換插槽的標籤,可是內容由子組件來提供。

咱們先提一個需求

子組件中包括一組數據,好比:pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']

須要在多個界面進行展現:

  • 某些界面是以水平方向一一展現的,
  • 某些界面是以列表形式展現的,
  • 某些界面直接展現一個數組

內容在子組件,但願父組件告訴咱們如何展現,怎麼辦呢

  • 利用slot做用域插槽就能夠了

咱們來看看子組件的定義:

image-20201128140613312

在父組件使用咱們的子組件時,從子組件中拿到數據

咱們經過 <template slot-scope="slotProps"> 獲取到 slotProps 屬性

在經過 slotProps.data 就能夠獲取到剛纔咱們傳入的data了

image-20201128140707214
相關文章
相關標籤/搜索