概述
上一篇我們重點介紹了元件的建立、註冊和使用,熟練這幾個步驟將有助於深入元件的開發。另外,在子元件中定義props,可以讓父元件的資料傳遞下來,這就好比子元件告訴父元件:“嘿,老哥,我開通了一個驛站,你把東西放到驛站我就可以拿到了。”
今天我們將著重介紹slot和父子元件之間的訪問和通訊,slot是一個非常有用的東西,它相當於一個內容插槽,它是我們重用元件的基礎。Vue的事件系統獨立於原生的DOM事件,它用於元件之間的通訊。
本文的主要內容如下:
- 元件的編譯作用域
- 在元件template中使用<slot>標籤作為內容插槽
- 使用$children, $refs, $parent 實現父子元件之間的例項訪問
- 在子元件中,使用$dispatch向父元件派發事件;在父元件中,使用$broadcast向子元件傳播事件
- 結合這些基礎知識,我們一步一步實現一個CURD的示例
Demo和原始碼已放到GitHub,如果您覺得本篇內容不錯,請點個贊,或在GitHub上加個星星!
注意:以下示例的元件模板都定義在<template>標籤中,然而IE不支援<template>標籤,這使得在IE中<template>標籤中的內容會顯示出來。解決辦法——隱藏<template>標籤
1 2 3 |
template{ display: none; } |
個瀏覽器對<template>標籤的支援情況,請參見:http://caniuse.com/#feat=template
編譯作用域
儘管使用元件就像使用一般的HTML元素一樣,但它畢竟不是標準的HTML元素,為了讓瀏覽器能夠識別它,元件會被解析為標準的HTML片段,然後將元件的標籤替換為該HTML片段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<div id="app"> <my-component> </my-component> </div> <template id="myComponent"> <div> <h2>{{ msg }}</h2> <button v-on:click="showMsg">Show Message</button> </div> </template> <script src="js/vue.js"></script> <script> new Vue({ el: '#app', components: { 'my-component': { template: '#myComponent', data: function() { return { msg: 'This is a component!' } }, methods: { showMsg: function() { alert(this.msg) } } } } }) |
這段程式碼定義了一個my-component元件,<my-component><my-component>不是標準的HTML元素,瀏覽器是不理解這個元素的。
那麼Vue是如何讓瀏覽器理解<my-component><my-component>標籤的呢?(下圖是我個人的理解)
在建立一個Vue例項時,除了將它掛載到某個HTML元素下,還要編譯元件,將元件轉換為HTML片段。
除此之外,Vue例項還會識別其所掛載的元素下的<my-component>標籤,然後將<my-component>標籤替換為HTML片段。
實際上瀏覽器仍然是不理解<my-component>標籤的,我們可以通過檢視原始碼瞭解到這一點。
元件在使用前,經過編譯已經被轉換為HTML片段了,元件是有一個作用域的,那麼元件的作用域是什麼呢?
你可以將它理解為元件模板包含的HTML片段,元件模板內容之外就不是元件的作用域了。
例如,my-component元件的作用域只是下面這個小片段。
元件的模板是在其作用域內編譯的,那麼元件選項物件中的資料也應該是在元件模板中使用的。
考慮下面的程式碼,在Vue例項和元件的data選項中分別追加一個display屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
new Vue({ el: '#app', data: { display: true }, components: { 'my-component': { template: '#myComponent', data: function() { return { msg: 'This is a component!', display: false } }, methods: { showMsg: function() { alert(this.msg) } } } } }) |
然後在my-component標籤上使用指令v-show="display"
,這個display資料是來源於Vue例項,還是my-component元件呢?
1 2 3 4 |
<div id="app"> <my-component v-show="display"> </my-component> </div> |
答案是Vue例項。
至此,我們應該認識到元件的作用域是獨立的:
通俗地講,在子元件中定義的資料,只能用在子元件的模板。在父元件中定義的資料,只能用在父元件的模板。如果父元件的資料要在子元件中使用,則需要子元件定義props。
使用Slot
為了讓元件可以組合,我們需要一種方式來混合父元件的內容與子元件自己的模板。這個處理稱為內容分發,Vue.js 實現了一個內容分發 API,使用特殊的 <slot> 元素作為原始內容的插槽。
如果不理解這段話,可以先跳過,你只要知道<slot>元素是一個內容插槽。
單個Slot
下面的程式碼在定義my-component元件的模板時,指定了一個<slot></slot>元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<div id="app"> <my-component> <h1>Hello Vue.js!</h1> </my-component> <my-component> </my-component> </div> <template id="myComponent"> <div class="content"> <h2>This is a component!</h2> <slot>如果沒有分發內容,則顯示slot中的內容</slot> <p>Say something...</p> </div> </template> <script src="js/vue.js"></script> <script> Vue.component('my-component', { template: '#myComponent' }) new Vue({ el: '#app' }) </script> |
這段程式碼執行結果如下:
第一個<my-component>標籤有一段分發內容<h1>Hello Vue.js!</h1>
,渲染元件時顯示了這段內容。
第二個<my-component>標籤則沒有,渲染元件時則顯示了slot標籤中的內容。
指定名稱的slot
上面這個示例是一個匿名slot,它只能表示一個插槽。如果需要多個內容插槽,則可以為slot元素指定name屬性。
多個slot一起使用時,會非常有用。例如,對話方塊是HTML常用的一種互動方式。
在不同的運用場景下,對話方塊的頭部、主體內容、底部可能是不一樣的。
這時,使用不同名稱的slot就能輕易解決這個問題了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<template id="dialog-template"> <div class="dialogs"> <div class="dialog" v-bind:class="{ 'dialog-active': show }"> <div class="dialog-content"> <div class="close rotate"> <span class="iconfont icon-close" @click="close"></span> </div> <slot name="header"></slot> <slot name="body"></slot> <slot name="footer"></slot> </div> </div> <div class="dialog-overlay"></div> </div> </template> <script src="js/vue.js"></script> <script> Vue.component('modal-dialog', { template: '#dialog-template', props: ['show'], methods: { close: function() { this.show = false } } }) new Vue({ el: '#app', data: { show: false }, methods: { openDialog: function() { this.show = true }, closeDialog: function() { this.show = false } } }) </script> |
在定義modal-dialog元件的template時,我們使用了3個slot,它們的name特性分別是header、body和footer。
在<modal-dialog>標籤下,分別為三個元素指定slot特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<div id="app"> <modal-dialog v-bind:show.sync="show"> <header class="dialog-header" slot="header"> <h1 class="dialog-title">提示資訊</h1> </header> <div class="dialog-body" slot="body"> <p>你想在對話方塊中放什麼內容都可以!</p> <p>你可以放一段文字,也可以放一些表單,或者是一些圖片。</p> </div> <footer class="dialog-footer" slot="footer"> <button class="btn" @click="closeDialog">關閉</button> </footer> </modal-dialog> <button class="btn btn-open" @click="openDialog">開啟對話方塊</button> </div> |
對話方塊的標題內容、主體內容、底部內容,完全由我們自定義,而且這些內容就是一些簡單的HTML元素!
如果需要定製對話方塊的樣式,我們只需要在<modal-dialog>上追加一個v-bind指令,讓它繫結一個class。
1 |
<modal-dialog v-bind:show.sync="show" v-bind:class="dialogClass"> |
然後修改一下Vue例項,在data選項中追加一個dialogClass屬性,然後修改openDialog()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
new Vue({ el: '#app', data: { show: false, dialogClass: 'dialog-info' }, methods: { openDialog: function(dialogClass) { this.show = true this.dialogClass = dialogClass }, closeDialog: function() { this.show = false } } }) |
雖然我們在modal-dialog元件中定義了3個slot,但是在頁面中使用它時,並不用每次都指定這3個slot。
比如,有時候我們可能只需要header和body:
1 2 3 4 5 6 7 8 9 10 |
<modal-dialog v-bind:show.sync="show" v-bind:class="dialogClass"> <header class="dialog-header" slot="header"> <h1 class="dialog-title">提示資訊</h1> </header> <div class="dialog-body" slot="body"> <p>你想在對話方塊中放什麼內容都可以!</p> <p>你可以放一段文字,也可以放一些表單,或者是一些圖片。</p> </div> </modal-dialog> |
現在看到的對話方塊是沒有底部的,只有標題和主體內容。
多個slot同時使用的場景還有很多,例如:使用者的註冊、登入、找回密碼等這些表單集合,也可以用一個元件來完成。
父子元件之間的訪問
有時候我們需要父元件訪問子元件,子元件訪問父元件,或者是子元件訪問根元件。
針對這幾種情況,Vue.js都提供了相應的API:
- 父元件訪問子元件:使用
$children
或$refs
- 子元件訪問父元件:使用
$parent
- 子元件訪問根元件:使用
$root
$children示例
下面這段程式碼定義了3個元件:父元件parent-component,兩個子元件child-component1和child-component2。
在父元件中,通過this.$children
可以訪問子元件。
this.$children
是一個陣列,它包含所有子元件的例項。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<div id="app"> <parent-component></parent-component> </div> <template id="parent-component"> <child-component1></child-component1> <child-component2></child-component2> <button v-on:click="showChildComponentData">顯示子元件的資料</button> </template> <template id="child-component1"> <h2>This is child component 1</h2> </template> <template id="child-component2"> <h2>This is child component 2</h2> </template> <script src="js/vue.js"></script> <script> Vue.component('parent-component', { template: '#parent-component', components: { 'child-component1': { template: '#child-component1', data: function() { return { msg: 'child component 111111' } } }, 'child-component2': { template: '#child-component2', data: function() { return { msg: 'child component 222222' } } } }, methods: { showChildComponentData: function() { for (var i = 0; i < this.$children.length; i++) { alert(this.$children[i].msg) } } } }) new Vue({ el: '#app' }) </script> |
$refs示例
元件個數較多時,我們難以記住各個元件的順序和位置,通過序號訪問子元件不是很方便。
在子元件上使用v-ref指令,可以給子元件指定一個索引ID:
1 2 3 4 5 |
<template id="parent-component"> <child-component1 v-ref:cc1></child-component1> <child-component2 v-ref:cc2></child-component2> <button v-on:click="showChildComponentData">顯示子元件的資料</button> </template> |
在父元件中,則通過$refs.索引ID
訪問子元件的例項:
1 2 3 4 |
showChildComponentData: function() { alert(this.$refs.cc1.msg); alert(this.$refs.cc2.msg); } |
$parent示例
下面這段程式碼定義了兩個元件:child-component和它的父元件parent-component。
在子元件中,通過this.$parent可以訪問到父元件的例項。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<div id="app"> <parent-component></parent-component> </div> <template id="parent-component"> <child-component></child-component> </template> <template id="child-component"> <h2>This is a child component</h2> <button v-on:click="showParentComponentData">顯示父元件的資料</button> </template> <script src="js/vue.js"></script> <script> Vue.component('parent-component', { template: '#parent-component', components: { 'child-component': { template: '#child-component', methods: { showParentComponentData: function() { alert(this.$parent.msg) } } } }, data: function() { return { msg: 'parent component message' } } }) new Vue({ el: '#app' }) </script> |
注意:儘管可以訪問父鏈上任意的例項,不過子元件應當避免直接依賴父元件的資料,儘量顯式地使用 props 傳遞資料。另外,在子元件中修改父元件的狀態是非常糟糕的做法,因為:
1.這讓父元件與子元件緊密地耦合;
2. 只看父元件,很難理解父元件的狀態。因為它可能被任意子元件修改!理想情況下,只有元件自己能修改它的狀態。
自定義事件
有時候我們希望觸發父元件的某個事件時,可以通知到子元件;觸發子元件的某個事件時,可以通知到父元件。
Vue 例項實現了一個自定義事件介面,用於在元件樹中通訊。這個事件系統獨立於原生 DOM 事件,用法也不同。
每個 Vue 例項都是一個事件觸發器:
- 使用
$on()
監聽事件; - 使用
$emit()
在它上面觸發事件; - 使用
$dispatch()
派發事件,事件沿著父鏈冒泡; - 使用
$broadcast()
廣播事件,事件向下傳導給所有的後代。
派發事件
下面這段程式碼是一個簡單的事件派發處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<div id="app"> <p>Messages: {{ messages | json }}</p> <child-component></child-component> </div> <template id="child-component"> <input v-model="msg" /> <button v-on:click="notify">Dispatch Event</button> </template> <script src="js/vue.js"></script> <script> // 註冊子元件 Vue.component('child-component', { template: '#child-component', data: function() { return { msg: '' } }, methods: { notify: function() { if (this.msg.trim()) { this.$dispatch('child-msg', this.msg) this.msg = '' } } } }) // 初始化父元件 new Vue({ el: '#app', data: { messages: [] }, events: { 'child-msg': function(msg) { this.messages.push(msg) } } }) </script> |
我們將這個示例分為幾個步驟解讀:
- 子元件的button元素繫結了click事件,該事件指向
notify
方法 - 子元件的
notify
方法在處理時,呼叫了$dispatch
,將事件派發到父元件的child-msg
事件,並給該該事件提供了一個msg引數 - 父元件的events選項中定義了
child-msg
事件,父元件接收到子元件的派發後,呼叫child-msg
事件。
執行結果如下:
廣播事件
下面這段程式碼是一個簡單的事件廣播處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<div id="app"> <input v-model="msg" /> <button v-on:click="notify">Broadcast Event</button> <child-component></child-component> </div> <template id="child-component"> <ul> <li v-for="item in messages"> 父元件錄入了資訊:{{ item }} </li> </ul> </template> <script src="js/vue.js"></script> <script> // 註冊子元件 Vue.component('child-component', { template: '#child-component', data: function() { return { messages: [] } }, events: { 'parent-msg': function(msg) { this.messages.push(msg) } } }) // 初始化父元件 new Vue({ el: '#app', data: { msg: '' }, methods: { notify: function() { if (this.msg.trim()) { this.$broadcast('parent-msg', this.msg) } } } }) </script> |
我們將這個示例分為幾個步驟解讀:
- 父元件的button元素繫結了click事件,該事件指向
notify
方法 - 父元件的
notify
方法在處理時,呼叫了$broadcast
,將事件派發到子元件的parent-msg
事件,並給該該事件提供了一個msg引數 - 子元件的events選項中定義了
parent-msg
事件,子元件接收到父元件的廣播後,呼叫parent-msg
事件。
執行結果如下:
CURD示例
Vue.js元件的API來源於三部分——prop,slot和事件。
- prop 允許外部環境傳遞資料給元件;
- 事件 允許元件觸發外部環境的 action;
- slot 允許外部環境插入內容到元件的檢視結構內。
至此,這三部分我都已經介紹完了,接下來我就用這些知識來教大家一步一步完成一個CURD示例。
第1步——建立表格元件,新增查詢和刪除功能
建立表格元件,新增過濾,資料刪除功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
<div id="app"> <div class="container"> <div class="form-group"> <label>Search</label> <input type="text" class="search-input" v-model="searchQuery" /> </div> </div> <div class="container"> <simple-grid :data-list="people" :columns="columns" :search-key="searchQuery"> </simple-grid> </div> </div> <template id="grid-template"> <table> <thead> <tr> <th v-for="col in columns"> {{ col.name | capitalize}} </th> <th> Delete </th> </tr> </thead> <tbody> <tr v-for="(index,entry) in dataList | filterBy searchKey"> <td v-for="col in columns"> {{entry[col.name]}} </td> <td class="text-center"> <button @click="deleteItem(index)">delete</button> </td> </tr> </tbody> </table> </template> <script src="../js/vue.js"></script> <script> Vue.component('simple-grid', { template: '#grid-template', props: ['dataList', 'columns', 'searchKey'], methods: { deleteItem: function(index) { this.dataList.splice(index, 1); }, } }) var demo = new Vue({ el: '#app', data: { searchQuery: '', columns: [{ name: 'name' }, { name: 'age' }, { name: 'sex' }], people: [{ name: 'Jack', age: 30, sex: 'Male' }, { name: 'Bill', age: 26, sex: 'Male' }, { name: 'Tracy', age: 22, sex: 'Female' }, { name: 'Chris', age: 36, sex: 'Male' }] } }) </script> |
使用知識點
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. 使用陣列索引別名
陣列預設的索引名稱為$index
,v-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-list
是v-bind:data-list
的縮寫,:columns
是v-bind:columns
的縮寫,:search-key
是v-bind:search-key
的縮寫。
<button @click="deleteItem(index)">delete</button>
使用了v-on指令的縮寫,@click
是v-on:click
的縮寫。
第2步——建立對話方塊元件
表格資料的新增和修改,我們使用模態對話方塊來實現。
模態對話方塊有兩種模式,新建模式和修改模式,分別用於新建一條資料和修改指定的資料。
由於對話方塊的內容來源於具體的資料,所以我們可以考慮將對話方塊作為simple-grid元件的一個子元件。
modal-dialog元件的模板內容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<template id="dialog-template"> <div class="dialogs"> <div class="dialog" v-bind:class="{ 'dialog-active': show }"> <div class="dialog-content"> <header class="dialog-header"> <h1 class="dialog-title">{{ title }}</h1> </header> <footer class="dialog-footer"> <div class="form-group"> <label></label> <button v-on:click="save">Save</button> <button v-on:click="close">Close</button> </div> </footer> </div> </div> <div class="dialog-overlay"></div> </div> </template> |
modal-dialog元件在simple-grid元件中註冊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
Vue.component('simple-grid', { // ...已省略 data: function() { return { mode: 0, item: {} titie: '' } }, components: { 'modal-dialog': { template: '#dialog-template', data: function() { return { // 對話方塊預設是不顯示的 show: false } }, /* * mode = 1是新增資料模式,mode = 2是修改資料模式 * title表示對話方塊的標題內容 * fields表示對話方塊要顯示的資料欄位陣列 * item是由simple-dialog傳下來,用於繫結表單欄位的 */ props: ['mode', 'title', 'fields', 'item'], methods: { close: function() { this.show = false }, save: function() { } } } } // ...已省略 }) |
由於modal-dialog元件是simple-grid的子元件,所以它應該在simple-grid的template中使用:
1 2 3 4 5 6 |
<template id="grid-template"> <!--...前面的內容已省略 --> <modal-dialog :mode="mode" :title="title" :fields="columns" :item="item"> </modal-dialog> <!--...後面的內容已省略 --> </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步——實現資料新建功能
1. 修改Vue例項的data選項的columns:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var demo = new Vue({ // ...已省略 columns: [{ name: 'name', isKey: true }, { name: 'age' }, { name: 'sex', dataSource: ['Male', 'Female'] }] // ...已省略 }) |
為’name’列追加一個isKey屬性,並設定為true,表示該列為主鍵列。
為’sex’列追加一個dataSoruce屬性,並設定為[‘Male’, ‘Female’],表示新增或修改資料時選擇性別的下拉框資料來源。
2. 修改modal-dialog的template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<template id="dialog-template"> <div class="dialogs"> <div class="dialog" v-bind:class="{ 'dialog-active': show }"> <div class="dialog-content"> <header class="dialog-header"> <h1 class="dialog-title">{{ title }}</h1> </header> <div v-for="field in fields" class="form-group" > <label>{{ field.name }}</label> <select v-if="field.dataSource" v-model="item[field.name]"> <option v-for="opt in field.dataSource" :value="opt">{{ opt }}</option> </select> <input v-else type="text" v-model="item[field.name]"> </div> <footer class="form-group"> <label></label> <button v-on:click="save">Save</button> <button v-on:click="close">Close</button> </footer> </div> </div> <div class="dialog-overlay"></div> </div> </template> |
在modal-dialog元件的模板中遍歷fields,然後顯示field的名稱。
在渲染表單時,根據是否有dataSource判定表單是下拉框還是文字框。
(由於示例較為簡陋,所以只提供了input和select兩種表單型別)
注意modal-dialog元件的fields是由Vue例項傳遞給simple-grid,然後再由simple-grid傳遞過來的。
3. 修改simple-grid的template
1 2 3 4 5 6 7 8 9 |
<template id="grid-template"> <!--...已省略 --> <div class="container"> <button class="btn" @click="openNewItemDialog('Create new item')">Create</button> </div> <modal-dialog :mode="mode" :title="title" :fields="columns" :item="item" v-on:create-item="createItem"> </modal-dialog> </template> |
新增一個Create按鈕,繫結click事件到openNewItemDiaolog()
方法,該方法用於開啟modal-dialog元件,並將模式設定為新建模式。
在<modal-dialog>標籤上給sample-grid繫結一個自定義事件create-item
,後面在$dispatch派發事件時會用到。
4. 修改simple-grid的methods選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
Vue.component('simple-grid', { // ...已省略 methods: { openNewItemDialog: function(title) { // 對話方塊的標題 this.title = title // mode = 1表示新建模式 this.mode = 1 // 初始化this.item this.item = {} // 廣播事件,showDialog是modal-dialog元件的一個方法,傳入引數true表示顯示對話方塊 this.$broadcast('showDialog', true) }, createItem: function() { // 將item追加到dataList this.dataList.push(this.item) // 廣播事件,傳入引數false表示隱藏對話方塊 this.$broadcast('showDialog', false) // 新建完資料後,重置item物件 this.item = {} }, deleteItem: function(index) { this.dataList.splice(index, 1); }, }, // ...已省略 }) |
追加了兩個方法:opeNewItemDialog
和createItem
方法。
opeNewItemDialog
方法用於開啟對話方塊,this.$broadcast('showDialog', true)
呼叫子元件modal-dialog的showDialog
事件,傳入引數true表示顯示對話方塊。
createItem
方法用於儲存新建的資料,this.$broadcast('showDialog', false)
呼叫子元件modal-dialog的showDialog
事件,傳入引數false表示隱藏對話方塊。
5. 修改modal-grid的methods和events選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Vue.component('simple-grid', { // ...已省略 components: { 'modal-dialog': { // ...已省略 methods: { close: function() { this.show = false }, save: function() { //新建模式 if (this.mode === 1){ // 使用$dispatch呼叫simple-grid的create-item方法 this.$dispatch('create-item') } } }, events: { // 顯示或隱藏對話方塊 'showDialog': function(show) { this.show = show } } } } // ...已省略 }) |
修改methods選項的save
方法,由於儲存按鈕是在子元件modal-dialog中的,而createItem方法是在父元件simple-grid中的,所以這裡使用this.$dispatch('create-item')
派發到父元件的自定義事件create-item
。
追加events選項,新增showDialog事件,用於顯示或隱藏對話方塊。
請將4和5結合起來看,我們既用到了$broadcast
廣播事件,又用到了$dispatch
派發事件。
下面這幅圖有助於理解simple-grid和modal-dialog元件之間的通訊:
create-item
是一個自定義事件,由子元件modal-dialog呼叫this.$dispatch('create-item')
派發到自定義事件create-item
,自定義事件create-item
是繫結在父元件simple-grid上的,該事件會執行createItem
方法。
第4步——實現資料修改功能
1. 修改sample-grid的template
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<template id="grid-template"> <!--...已省略--> <tbody> <tr v-for="(index,entry) in dataList | filterBy searchKey"> <td v-for="col in columns"> <span v-if="col.isKey"><a href="javascript:void(0)" @click="openEditItemDialog(index, 'Edit item ' + entry[col.name])">{{entry[col.name]}}</a></span> <span v-else>{{entry[col.name]}}</span> </td> </tr> </tbody> <!--...已省略--> <modal-dialog :mode="mode" :title="title" :item="item" :fields="columns" v-on:create-item="createItem" v-on:update-item="updateItem"> </modal-dialog> </template> |
遍歷列表資料時,使用v-if指令判斷當前列是否為主鍵列,如果是主鍵列,則給主鍵列新增連結,然後給連結繫結click事件,click事件用於開啟修改資料的對話方塊。
在<modal-dialog>標籤上,給sample-grid繫結自定義事件update-item
,update-item
事件指向sample-grid的方法updateItem
。
2. 修改modal-dialog的template
1 2 3 4 5 6 7 |
<div v-for="field in fields" class="form-group"> <label>{{ field.name }}</label> <select v-if="field.dataSource" v-model="item[field.name]" :disabled="mode === 2 && field.isKey"> <option v-for="opt in field.dataSource" :value="opt">{{ opt }}</option> </select> <input v-else type="text" v-model="item[field.name]" :disabled="mode === 2 && field.isKey"> </div> |
在修改模式下(mode = 2),如果當前欄位是主鍵欄位,則禁止修改。
3. 修改sample-grid的methods選項
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
// 彈出修改資料的對話方塊時,使用物件的深拷貝 initItemForUpdate: function(p) { var c = c || {}; for (var i in p) { // 屬性i是否為p物件的自有屬性 if (p.hasOwnProperty(i)) { if (typeof p[i] === 'object') { c[i] = Array.isArray(p[i]) ? [] : {} deepCopy(p[i], c[i]) } else { // 屬性是基礎型別時,直接拷貝 c[i] = p[i] } } } return c; }, findItemByKey: function(key){ var keyColumn = this.keyColumn for(var i = 0; i < this.dataList.length; i++){ if(this.dataList[i][keyColumn] === key){ return this.dataList[i] } } }, createItem: function() { // 將item追加到dataList this.dataList.push(this.item) // 廣播事件,傳入引數false表示隱藏對話方塊 this.$broadcast('showDialog', false) // 新建完資料後,重置item物件 this.item = {} }, updateItem: function() { // 獲取主鍵列 var keyColumn = this.keyColumn for (var i = 0; i < this.dataList.length; i++) { // 根據主鍵查詢要修改的資料,然後將this.item資料更新到this.dataList[i] if (this.dataList[i][keyColumn] === this.item[keyColumn]) { for (var j in this.item) { this.dataList[i][j] = this.item[j] } break; } } // 廣播事件,傳入引數false表示隱藏對話方塊 this.$broadcast('showDialog', false) // 修改完資料後,重置item物件 this.item = {} } |
追加的內容:呼叫內建的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]
是一個引用物件,它有一些屬性也是引用型別的,如果使用淺拷貝可能得到一些超出預期的效果。
4.修改modal-dialog的methods選項
1 2 3 4 5 6 7 8 9 10 |
save: function() { //新建模式 if (this.mode === 1) { // 使用$dispatch呼叫simple-grid的create-item事件 this.$dispatch('create-item') }else if(this.mode === 2){ // 使用$dispatch呼叫simple-grid的update-item事件 this.$dispatch('update-item') } } |
修改methods選項中的save方法,this.mode === 2時,將事件派發到父元件的update-item
事件。
第5步——修改資料新建功能
修改sample-grid的methods選項,追加itemExists方法,然後修改createItem方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
itemExists: function(keyColumn) { for (var i = 0; i < this.dataList.length; i++) { if (this.item[keyColumn] === this.dataList[i][keyColumn]) return true; } return false; }, createItem: function() { var keyColumn = this.getKeyColumn() if (!this.itemExists(keyColumn)) { // 將item追加到dataList this.dataList.push(this.item) // 廣播事件,傳入引數false表示隱藏對話方塊 this.$broadcast('showDialog', false) // 新建完資料後,重置item物件 this.item = {} } else { alert(keyColumn + ' "' + this.item[keyColumn] + '" is already exists') } } |
由於主鍵列資料是不能重複的,所以在新增資料時需要判斷主鍵列資料是否已經存在。
總結
說到底,元件的API主要來源於以下三部分:
- prop 允許外部環境傳遞資料給元件;
- 事件 允許元件觸發外部環境的 action;
- slot 允許外部環境插入內容到元件的檢視結構內。
這三大知識點在上下兩篇文章中都體現出來了,限於篇幅和個人知識的匱乏,我並不能將元件的所有特性都描述出來,這還需要靠各位花一些時間去多多瞭解官網的API,並付諸實踐。
如果要構建一些大型的應用,基於元件的開發模式是一個不錯的選擇,我們將整個系統拆分成一個一個小元件,就像樂高一樣,然後將這些元件拼接起來。