Vue.js——60分鐘元件快速入門(下篇)

發表於2016-08-30

概述

上一篇我們重點介紹了元件的建立、註冊和使用,熟練這幾個步驟將有助於深入元件的開發。另外,在子元件中定義props,可以讓父元件的資料傳遞下來,這就好比子元件告訴父元件:“嘿,老哥,我開通了一個驛站,你把東西放到驛站我就可以拿到了。”

今天我們將著重介紹slot和父子元件之間的訪問和通訊,slot是一個非常有用的東西,它相當於一個內容插槽,它是我們重用元件的基礎。Vue的事件系統獨立於原生的DOM事件,它用於元件之間的通訊。

本文的主要內容如下:

  • 元件的編譯作用域
  • 在元件template中使用<slot>標籤作為內容插槽
  • 使用$children, $refs, $parent 實現父子元件之間的例項訪問
  • 在子元件中,使用$dispatch向父元件派發事件;在父元件中,使用$broadcast向子元件傳播事件
  • 結合這些基礎知識,我們一步一步實現一個CURD的示例

Demo和原始碼已放到GitHub,如果您覺得本篇內容不錯,請點個贊,或在GitHub上加個星星!

注意:以下示例的元件模板都定義在<template>標籤中,然而IE不支援<template>標籤,這使得在IE中<template>標籤中的內容會顯示出來。解決辦法——隱藏<template>標籤

個瀏覽器對<template>標籤的支援情況,請參見:http://caniuse.com/#feat=template

編譯作用域

儘管使用元件就像使用一般的HTML元素一樣,但它畢竟不是標準的HTML元素,為了讓瀏覽器能夠識別它,元件會被解析為標準的HTML片段,然後將元件的標籤替換為該HTML片段。

這段程式碼定義了一個my-component元件,<my-component><my-component>不是標準的HTML元素,瀏覽器是不理解這個元素的。
那麼Vue是如何讓瀏覽器理解<my-component><my-component>標籤的呢?(下圖是我個人的理解)

image

在建立一個Vue例項時,除了將它掛載到某個HTML元素下,還要編譯元件,將元件轉換為HTML片段。
除此之外,Vue例項還會識別其所掛載的元素下的<my-component>標籤,然後將<my-component>標籤替換為HTML片段。

實際上瀏覽器仍然是不理解<my-component>標籤的,我們可以通過檢視原始碼瞭解到這一點。

image

元件在使用前,經過編譯已經被轉換為HTML片段了,元件是有一個作用域的,那麼元件的作用域是什麼呢?
你可以將它理解為元件模板包含的HTML片段,元件模板內容之外就不是元件的作用域了。
例如,my-component元件的作用域只是下面這個小片段。

image

元件的模板是在其作用域內編譯的,那麼元件選項物件中的資料也應該是在元件模板中使用的。
考慮下面的程式碼,在Vue例項和元件的data選項中分別追加一個display屬性:

然後在my-component標籤上使用指令v-show="display",這個display資料是來源於Vue例項,還是my-component元件呢?

答案是Vue例項。

至此,我們應該認識到元件的作用域是獨立的:

父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯

通俗地講,在子元件中定義的資料,只能用在子元件的模板。在父元件中定義的資料,只能用在父元件的模板。如果父元件的資料要在子元件中使用,則需要子元件定義props。

使用Slot

為了讓元件可以組合,我們需要一種方式來混合父元件的內容與子元件自己的模板。這個處理稱為內容分發,Vue.js 實現了一個內容分發 API,使用特殊的 <slot> 元素作為原始內容的插槽。

如果不理解這段話,可以先跳過,你只要知道<slot>元素是一個內容插槽。

單個Slot

下面的程式碼在定義my-component元件的模板時,指定了一個<slot></slot>元素。

這段程式碼執行結果如下:

image

第一個<my-component>標籤有一段分發內容<h1>Hello Vue.js!</h1>,渲染元件時顯示了這段內容。image

第二個<my-component>標籤則沒有,渲染元件時則顯示了slot標籤中的內容。

View Demo

指定名稱的slot

上面這個示例是一個匿名slot,它只能表示一個插槽。如果需要多個內容插槽,則可以為slot元素指定name屬性。

多個slot一起使用時,會非常有用。例如,對話方塊是HTML常用的一種互動方式。
在不同的運用場景下,對話方塊的頭部、主體內容、底部可能是不一樣的。

image

這時,使用不同名稱的slot就能輕易解決這個問題了。

在定義modal-dialog元件的template時,我們使用了3個slot,它們的name特性分別是header、body和footer。

在<modal-dialog>標籤下,分別為三個元素指定slot特性:

對話方塊的標題內容、主體內容、底部內容,完全由我們自定義,而且這些內容就是一些簡單的HTML元素!

View Demo

12

如果需要定製對話方塊的樣式,我們只需要在<modal-dialog>上追加一個v-bind指令,讓它繫結一個class。

然後修改一下Vue例項,在data選項中追加一個dialogClass屬性,然後修改openDialog()方法:

View Demo

13

雖然我們在modal-dialog元件中定義了3個slot,但是在頁面中使用它時,並不用每次都指定這3個slot。
比如,有時候我們可能只需要header和body:

現在看到的對話方塊是沒有底部的,只有標題和主體內容。

View Demo

14

多個slot同時使用的場景還有很多,例如:使用者的註冊、登入、找回密碼等這些表單集合,也可以用一個元件來完成。

父子元件之間的訪問

有時候我們需要父元件訪問子元件,子元件訪問父元件,或者是子元件訪問根元件。
針對這幾種情況,Vue.js都提供了相應的API:

  • 父元件訪問子元件:使用$children或$refs
  • 子元件訪問父元件:使用$parent
  • 子元件訪問根元件:使用$root

$children示例

下面這段程式碼定義了3個元件:父元件parent-component,兩個子元件child-component1和child-component2。

在父元件中,通過this.$children可以訪問子元件。
this.$children是一個陣列,它包含所有子元件的例項。

View Demo

15

$refs示例

元件個數較多時,我們難以記住各個元件的順序和位置,通過序號訪問子元件不是很方便。
在子元件上使用v-ref指令,可以給子元件指定一個索引ID:

在父元件中,則通過$refs.索引ID訪問子元件的例項:

$parent示例

下面這段程式碼定義了兩個元件:child-component和它的父元件parent-component。
在子元件中,通過this.$parent可以訪問到父元件的例項。

View Demo

16

注意:儘管可以訪問父鏈上任意的例項,不過子元件應當避免直接依賴父元件的資料,儘量顯式地使用 props 傳遞資料。另外,在子元件中修改父元件的狀態是非常糟糕的做法,因為:
1.這讓父元件與子元件緊密地耦合;
2. 只看父元件,很難理解父元件的狀態。因為它可能被任意子元件修改!理想情況下,只有元件自己能修改它的狀態。

自定義事件

有時候我們希望觸發父元件的某個事件時,可以通知到子元件;觸發子元件的某個事件時,可以通知到父元件。
Vue 例項實現了一個自定義事件介面,用於在元件樹中通訊。這個事件系統獨立於原生 DOM 事件,用法也不同。

每個 Vue 例項都是一個事件觸發器:

  • 使用 $on() 監聽事件;
  • 使用 $emit() 在它上面觸發事件;
  • 使用 $dispatch() 派發事件,事件沿著父鏈冒泡;
  • 使用 $broadcast() 廣播事件,事件向下傳導給所有的後代。

派發事件

下面這段程式碼是一個簡單的事件派發處理

View Demo

我們將這個示例分為幾個步驟解讀:

  1. 子元件的button元素繫結了click事件,該事件指向notify方法
  2. 子元件notify方法在處理時,呼叫了$dispatch,將事件派發到父元件child-msg事件,並給該該事件提供了一個msg引數
  3. 父元件的events選項中定義了child-msg事件,父元件接收到子元件的派發後,呼叫child-msg事件。

image

執行結果如下:

17

廣播事件

下面這段程式碼是一個簡單的事件廣播處理

View Demo

我們將這個示例分為幾個步驟解讀:

  1. 父元件的button元素繫結了click事件,該事件指向notify方法
  2. 父元件notify方法在處理時,呼叫了$broadcast,將事件派發到子元件parent-msg事件,並給該該事件提供了一個msg引數
  3. 子元件的events選項中定義了parent-msg事件,子元件接收到父元件的廣播後,呼叫parent-msg事件。

image

執行結果如下:

18

CURD示例

Vue.js元件的API來源於三部分——prop,slot和事件。

  • prop 允許外部環境傳遞資料給元件;
  • 事件 允許元件觸發外部環境的 action;
  • slot 允許外部環境插入內容到元件的檢視結構內。

至此,這三部分我都已經介紹完了,接下來我就用這些知識來教大家一步一步完成一個CURD示例。

第1步——建立表格元件,新增查詢和刪除功能

建立表格元件,新增過濾,資料刪除功能

20[3]

View Demo

使用知識點

1. 使用Vue.component語法糖

Vue.component是建立並註冊元件的語法糖,使用Vue.component註冊的元件是全域性的。

2. 使用prop將父元件資料傳遞給子元件

#app元素是父元件,simple-grid是子元件。

在simple-grid元件中定義選項props: ['dataList', 'columns', 'searchKey']
在#app下使用<simple-grid :data-list="people" :columns="columns" :search-key="searchQuery"> 將資料傳遞給simple-grid元件

3. 使用過濾器

{{ col.name | capitalize}}使用了capitalize過濾器,將字串的首字母轉換為大寫後輸出。
filterBy filterKey使用了filterBy過濾器,根據指定條件過濾陣列元素,filterBy返回過濾後的陣列。

4. 使用陣列索引別名

陣列預設的索引名稱為$indexv-for="(index,entry) in dataList使用了陣列索引別名
括號中的第一個引數index是$index的別名,第二個引數是遍歷的陣列元素。

5. 使用了v-bind和v-on指令的縮寫

<simple-grid :data-list="people" :columns="columns" :search-key="searchQuery"> 使用了v-bind指令的縮寫。
:data-listv-bind:data-list的縮寫,:columnsv-bind:columns的縮寫,:search-keyv-bind:search-key的縮寫。

<button @click="deleteItem(index)">delete</button> 使用了v-on指令的縮寫,@clickv-on:click的縮寫。

第2步——建立對話方塊元件

表格資料的新增和修改,我們使用模態對話方塊來實現。
模態對話方塊有兩種模式,新建模式和修改模式,分別用於新建一條資料和修改指定的資料。
由於對話方塊的內容來源於具體的資料,所以我們可以考慮將對話方塊作為simple-grid元件的一個子元件。

modal-dialog元件的模板內容:

modal-dialog元件在simple-grid元件中註冊:

由於modal-dialog元件是simple-grid的子元件,所以它應該在simple-grid的template中使用:

modal-dialog元件的props選項,追加了3個元素:

  • title表示對話方塊的標題內容
  • fields表示對話方塊要顯示的資料欄位陣列
  • item用於繫結表單欄位,它是一個物件

注意:由於modal-dialog是一個子元件,它僅用於simple-grid元件的新增或修改模式,所以modal-dialog的template沒有使用<slot>元素

使用知識點

1. 使用元件的區域性註冊

modal-dialog元件沒有使用Vue.component進行全域性註冊,使用simple-grid元件components選項實現了區域性註冊。

2. 使用元件的data選項

元件的data選項必須以函式的方式返回。

第3步——實現資料新建功能

21

View Demo

1. 修改Vue例項的data選項的columns:

為’name’列追加一個isKey屬性,並設定為true,表示該列為主鍵列。
為’sex’列追加一個dataSoruce屬性,並設定為[‘Male’, ‘Female’],表示新增或修改資料時選擇性別的下拉框資料來源。

2. 修改modal-dialog的template:

在modal-dialog元件的模板中遍歷fields,然後顯示field的名稱。
在渲染表單時,根據是否有dataSource判定表單是下拉框還是文字框。
(由於示例較為簡陋,所以只提供了input和select兩種表單型別)

注意modal-dialog元件的fields是由Vue例項傳遞給simple-grid,然後再由simple-grid傳遞過來的。

image

3. 修改simple-grid的template

新增一個Create按鈕,繫結click事件到openNewItemDiaolog()方法,該方法用於開啟modal-dialog元件,並將模式設定為新建模式。
在<modal-dialog>標籤上給sample-grid繫結一個自定義事件create-item,後面在$dispatch派發事件時會用到。

4. 修改simple-grid的methods選項

追加了兩個方法:opeNewItemDialogcreateItem方法。

opeNewItemDialog方法用於開啟對話方塊,this.$broadcast('showDialog', true) 呼叫子元件modal-dialog的showDialog事件,傳入引數true表示顯示對話方塊。

createItem方法用於儲存新建的資料,this.$broadcast('showDialog', false) 呼叫子元件modal-dialog的showDialog事件,傳入引數false表示隱藏對話方塊。

5. 修改modal-grid的methods和events選項

修改methods選項的save方法,由於儲存按鈕是在子元件modal-dialog中的,而createItem方法是在父元件simple-grid中的,所以這裡使用this.$dispatch('create-item') 派發到父元件的自定義事件create-item

追加events選項,新增showDialog事件,用於顯示或隱藏對話方塊。

請將4和5結合起來看,我們既用到了$broadcast廣播事件,又用到了$dispatch派發事件。
下面這幅圖有助於理解simple-grid和modal-dialog元件之間的通訊:

image

create-item是一個自定義事件,由子元件modal-dialog呼叫this.$dispatch('create-item') 派發到自定義事件create-item,自定義事件create-item是繫結在父元件simple-grid上的,該事件會執行createItem方法。

第4步——實現資料修改功能

22

View Demo

1. 修改sample-grid的template

遍歷列表資料時,使用v-if指令判斷當前列是否為主鍵列,如果是主鍵列,則給主鍵列新增連結,然後給連結繫結click事件,click事件用於開啟修改資料的對話方塊。
在<modal-dialog>標籤上,給sample-grid繫結自定義事件update-itemupdate-item事件指向sample-grid的方法updateItem

2. 修改modal-dialog的template

在修改模式下(mode = 2),如果當前欄位是主鍵欄位,則禁止修改。

3. 修改sample-grid的methods選項

追加的內容:呼叫內建的ready()函式,openEditDialog、updateItem、findItemByKey和initItemForUpdate方法。

ready()函式會在編譯結束和 $el 第一次插入文件之後呼叫,你可以將其理解為jQuery中的document.ready()。
在ready()函式中,初始化keyColumn,keyColumn表示主鍵列,呼叫updateItem方法時,會根據主鍵資料找到dataList中匹配的元素。

opeEditItemDialog方法用於開啟對話方塊,this.$broadcast('showDialog', true) 呼叫子元件modal-dialog的showDialog事件,傳入引數true表示顯示對話方塊。

ready()函式沒有特別的業務邏輯,主要是獲取主鍵列,呼叫updateItem方法時,會根據主鍵資料找到dataList中匹配的元素。

updateItem方法用於儲存修改的資料,this.$broadcast('showDialog', false) 呼叫子元件modal-dialog的showDialog事件,傳入引數false表示隱藏對話方塊。

initItemForUpdate方法用於將選中的資料this.dataList[index]深拷貝到this.item。為什麼要使用深拷貝呢?因為this.dataList[index]是一個引用物件,它有一些屬性也是引用型別的,如果使用淺拷貝可能得到一些超出預期的效果。

image

4.修改modal-dialog的methods選項

修改methods選項中的save方法,this.mode === 2時,將事件派發到父元件的update-item事件。

第5步——修改資料新建功能

23

View Demo

修改sample-grid的methods選項,追加itemExists方法,然後修改createItem方法。

由於主鍵列資料是不能重複的,所以在新增資料時需要判斷主鍵列資料是否已經存在。

總結

說到底,元件的API主要來源於以下三部分:

  • prop 允許外部環境傳遞資料給元件;
  • 事件 允許元件觸發外部環境的 action;
  • slot 允許外部環境插入內容到元件的檢視結構內。

這三大知識點在上下兩篇文章中都體現出來了,限於篇幅和個人知識的匱乏,我並不能將元件的所有特性都描述出來,這還需要靠各位花一些時間去多多瞭解官網的API,並付諸實踐。

如果要構建一些大型的應用,基於元件的開發模式是一個不錯的選擇,我們將整個系統拆分成一個一個小元件,就像樂高一樣,然後將這些元件拼接起來。

相關文章