本文的Demo和源代碼已放到GitHub,若是您以爲本篇內容不錯,請點個贊,或在GitHub上加個星星!javascript
https://github.com/zwl-jasmine95/Vue_testcss
如下全部知識都是基於vue.js 2.0版本html
組件 (Component) 是 Vue.js 最強大的功能之一。組件能夠擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器爲它添加特殊功能。在有些狀況下,組件也能夠是原生 HTML 元素的形式,以 is
特性擴展。vue
組件的使用有兩個步驟:註冊組件 和 使用組件。java
(1)要註冊一個全局組件,可使用 :node
Vue.component(tagName, options)
例如:git
Vue.component('my-component', { // 選項 })
(2)建立根實例:github
//建立根實例 var vm = new Vue({ el:'#component-demo' });
案例:bootstrap
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>全局組件(component)的基本使用</title> 6 </head> 7 <body> 8 <div id="component-demo"> 9 <!-- 2. #component-demo是Vue實例掛載的元素,應該在掛載元素範圍內使用組件--> 10 <hello-component></hello-component> 11 </div> 12 13 <script type="text/javascript" src="../lib/js/vue.js"></script> 14 <script type="text/javascript"> 15 16 //1.全局組件註冊,並指定組件的標籤,組件的HTML標籤爲<hello-component> 17 Vue.component('hello-component',{ 18 template:'<h1>hello component!</h1>' 19 }); 20 21 //建立根實例 22 var vm = new Vue({ 23 el:'#component-demo' 24 }); 25 26 </script> 27 </body> 28 </html>
注意事項:api
- 對於自定義標籤名,Vue.js 不強制要求遵循 W3C 規則 (小寫,而且包含一個短槓),儘管遵循這個規則比較好。
- 註冊組件必須發生在根實例初始化前。
- 若是自定義標籤名使用的是駝峯式命名,在使用的時候仍然要在大寫字母處加上短槓,並將大寫字母改成小寫。例如:命名爲‘helloWorldComponent’,在使用時變爲<hello-world-component></hello-world-component>
調用Vue.component()
註冊組件時,組件的註冊是全局的,這意味着該組件能夠在任意Vue示例下使用。
若是不須要全局註冊,或者是讓組件使用在其它組件內,能夠用選項對象的components屬性實現局部註冊。
1 <div id="component-demo"> 2 <local-component></local-component> 3 </div> 4 <script type="text/javascript"> 5 6 var child = { 7 template:'<h1>局部組件的基本使用!</h1>' 8 }; 9 10 var vm = new Vue({ 11 el:'#component-demo', 12 components:{ 13 'local-component':child 14 } 15 }); 16 </script>
因爲local-component組件是註冊在#component-demo元素對應的Vue實例下的,因此它不能在其它Vue實例下使用。若是你這樣作了,瀏覽器會提示一個錯誤:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>局部組件</title> 6 </head> 7 <body> 8 9 <div id="component-demo"> 10 <local-component></local-component> 11 </div> 12 13 <div id="component-demo2"> 14 <!--這裏會報錯,由於local-component是#component-demo的局部組件,不能在其餘地方使用--> 15 <local-component></local-component> 16 </div> 17 18 <script type="text/javascript" src="../lib/js/vue.js"></script> 19 <script type="text/javascript"> 20 21 var child = { 22 template:'<h1>局部組件的基本使用!</h1>' 23 }; 24 25 var vm = new Vue({ 26 el:'#component-demo', 27 components:{ 28 'local-component':child 29 } 30 }); 31 32 var vm2 = new Vue({ 33 el:'#component-demo2' 34 }); 35 36 37 </script> 38 </body> 39 </html>
組件意味着協同工做,一般父子組件會是這樣的關係:組件 A 在它的模版中使用了組件 B。它們之間必然須要相互通訊:父組件要給子組件傳遞數據,子組件須要將它內部發生的事情告知給父組件。然而,在一個良好定義的接口中儘量將父子組件解耦是很重要的。這保證了每一個組件能夠在相對隔離的環境中書寫和理解,也大幅提升了組件的可維護性和可重用性。
在 Vue 中,父子組件的關係能夠總結爲 props down, events up。父組件經過 props 向下傳遞數據給子組件,子組件經過 events 給父組件發送消息。
當使用 DOM 做爲模版時 (例如,將 el
選項掛載到一個已存在的元素上), 你會受到 HTML 的一些限制,由於 Vue 只有在瀏覽器解析和標準化 HTML 後才能獲取模版內容。尤爲像這些元素 <ul>
,<ol>
,<table>
,<select>
限制了能被它包裹的元素,而一些像 <option>
這樣的元素只能出如今某些其它元素內部。
在自定義組件中使用這些受限制的元素時會致使一些問題,例如:
<table> <my-row>...</my-row> </table>
自定義組件 <my-row>
被認爲是無效的內容,所以在渲染的時候會致使錯誤。變通的方案是使用特殊的 is
屬性:
<table> <tr is="my-row"></tr> </table>
應當注意,若是您使用來自如下來源之一的字符串模板,這些限制將不適用:
<script type="text/x-template">
- JavaScript 內聯模版字符串
.vue
組件所以,有必要的話請使用字符串模版。
上述組件註冊方法在template選項中拼接HTML元素比較麻煩,這也致使了HTML和JavaScript的高耦合性。慶幸的是,Vue.js提供了兩種方式將定義在JavaScript中的HTML模板分離出來。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>使用script標籤</title> 6 </head> 7 <body> 8 <div id="demo"> 9 <my-component></my-component> 10 </div> 11 12 <script type="text/x-template" id="myComponent"> 13 <h1>組件使用之使用script標籤</h1> 14 </script> 15 16 <script type="text/javascript" src="../lib/js/vue.js"></script> 17 <script type="text/javascript"> 18 Vue.component('my-component',{ 19 template:'#myComponent' 20 }); 21 22 var vm = new Vue({ 23 el:'#demo' 24 }); 25 </script> 26 </body> 27 </html>
template選項如今再也不是HTML元素,而是一個id,Vue.js根據這個id查找對應的元素,而後將這個元素內的HTML做爲模板進行編譯。
注意:使用<script>標籤時,type指定爲text/x-template,意在告訴瀏覽器這不是一段js腳本,瀏覽器在解析HTML文檔時會忽略<script>標籤內定義的內容。
若是使用<template>
標籤,則不須要指定type屬性。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>使用template標籤</title> 6 </head> 7 <body> 8 <div id="demo"> 9 <my-component></my-component> 10 </div> 11 12 <template id="myComponent"> 13 <h1>組件使用之使用template標籤</h1> 14 </template> 15 16 <script type="text/javascript" src="../lib/js/vue.js"></script> 17 <script type="text/javascript"> 18 Vue.component('my-component',{ 19 template:'#myComponent' 20 }); 21 22 var vm = new Vue({ 23 el:'#demo' 24 }); 25 </script> 26 </body> 27 </html>
建議使用<script>或<template>標籤來定義組件的HTML模板。這使得HTML代碼和JavaScript代碼是分離的,便於閱讀和維護。
經過 Vue 構造器傳入的各類選項大多數均可以在組件裏用。data
是一個例外,它必須是函數。
若是這樣寫:
Vue.component('hello-component',{ template:'<h1>{{message}}</h1>', data:{ message:1 } });
會報錯:
因此應該改成:
Vue.component('my-component',{ template:'<h1>{{message}}</h1>', data:function () { return {message : 'data必須爲函數'}; } });
組件實例的做用域是孤立的。這意味着不能 (也不該該) 在子組件的模板內直接引用父組件的數據。要讓子組件使用父組件的數據,咱們須要經過子組件的 props 選項。
1 <div id="demo"> 2 <child-component message="hello props!"></child-component> 3 </div> 4 5 <template id="myComponent"> 6 <h1>{{message}}</h1> 7 </template> 8 9 <script type="text/javascript" src="../lib/js/vue.js"></script> 10 <script type="text/javascript"> 11 var child = { 12 // 聲明 props 13 props: ['message'], 14 // 就像 data 同樣,prop 能夠用在模板內 15 // 一樣也能夠在 vm 實例中像「this.message」這樣使用 16 template:'#myComponent' 17 }; 18 19 var vm = new Vue({ 20 el:'#demo', 21 components:{ 22 'child-component':child 23 } 24 }); 25 </script>
HTML 特性是不區分大小寫的。因此,當使用的不是字符串模版,camelCased (駝峯式) 命名的 prop 須要轉換爲相對應的 kebab-case (短橫線隔開式) 命名
在模板中,要動態地綁定父組件的數據到子模板的 props,與綁定到任何普通的HTML特性相相似,就是用 v-bind
。每當父組件的數據變化時,該變化也會傳導給子組件。
1 <body> 2 <div id="demo"> 3 父組件信息:<input type="text" v-model="parentMessage"> 4 <child-component v-bind:message="parentMessage"></child-component> 5 </div> 6 7 <template id="myComponent"> 8 <h1>子組件獲取的信息爲:{{message}}</h1> 9 </template> 10 11 <script type="text/javascript" src="../lib/js/vue.js"></script> 12 <script type="text/javascript"> 13 14 var child = { 15 props: ['message'], 16 template:'#myComponent' 17 }; 18 19 var vm = new Vue({ 20 el:'#demo', 21 data:{ 22 parentMessage:'這裏是父組件信息!' 23 }, 24 components:{ 25 'child-component':child 26 } 27 }); 28 </script> 29 </body>
初學者常犯的一個錯誤是使用字面量語法傳遞數值:
<!-- 傳遞了一個字符串 "1" --> <comp some-prop="1"></comp>由於它是一個字面 prop,它的值是字符串
"1"
而不是 number。若是想傳遞一個實際的 number,須要使用v-bind
,從而讓它的值被看成 JavaScript 表達式計算:<!-- 傳遞實際的 number --> <comp v-bind:some-prop="1"></comp>
prop 是單向綁定的:當父組件的屬性變化時,將傳導給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>props單項數據綁定</title> 6 <link rel="stylesheet" href="../lib/css/bootstrap.min.css"> 7 </head> 8 <body> 9 <div id="demo"> 10 <table class="table table-striped"> 11 <tr> 12 <td colspan="3">父組件數據</td> 13 </tr> 14 <tr> 15 <td>姓名</td> 16 <td>{{parentName}}</td> 17 <td><input type="text" v-model="parentName"/></td> 18 </tr> 19 <tr> 20 <td>年齡</td> 21 <td>{{parentAge}}</td> 22 <td><input type="text" v-model="parentAge"/></td> 23 </tr> 24 </table> 25 26 <!--注意這裏camelCased (駝峯式) 命名的 prop 須要轉換爲相對應的 kebab-case (短橫線隔開式) 命名--> 27 <child-component v-bind:child-name="parentName" v-bind:child-age="parentAge"></child-component> 28 29 </div> 30 31 <template id="myComponent"> 32 <table class="table table-striped"> 33 <tr> 34 <td colspan="3">子組件數據</td> 35 </tr> 36 <tr> 37 <td>姓名</td> 38 <td>{{childName}}</td> 39 </tr> 40 <tr> 41 <td>年齡</td> 42 <td>{{childAge}}</td> 43 </tr> 44 </table> 45 </template> 46 47 <script type="text/javascript" src="../lib/js/vue.js"></script> 48 <script type="text/javascript"> 49 50 var child = { 51 template:'#myComponent', 52 props: ['childName','childAge'] 53 54 }; 55 56 var vm = new Vue({ 57 el:'#demo', 58 data:{ 59 parentName:'*茉莉花開*', 60 parentAge:22 61 }, 62 components:{ 63 'child-component':child 64 } 65 }); 66 </script> 67 </body> 68 </html>
父組件的數據更改時,子組件的數據也跟着修改:
每次父組件更新時,子組件的全部 prop 都會更新爲最新值。這意味着你不該該在子組件內部改變 prop。若是你這麼作了,Vue 會在控制檯給出警告。
爲何咱們會有修改 prop 中數據的衝動呢?一般是這兩種緣由:
prop 做爲初始值傳入後,子組件想把它看成局部數據來用;
prop 做爲初始值傳入,由子組件處理成其它數據輸出。
對這兩種緣由,正確的應對方式是:
定義一個局部變量,並用 prop 的值初始化它:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
定義一個計算屬性,處理 prop 的值並返回。
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
針對上述demo代碼,咱們能夠稍做修改,將子組件從父組件獲取的姓名變爲新的姓名:
咱們能夠看到效果:
!!!在這裏,父組件的姓名改變時,子組件的姓名不會跟着改變。
注意:
1.在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,若是 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。
2.Vue 2.x相比較Vue 1.x而言,升級變化除了實現了Virtual-Dom之外,給使用者最大不適就是移除的組件的
props
的雙向綁定功能。以往在Vue1.x中利用props
的twoWay
和.sync
綁定修飾符就能夠實現props的雙向綁定功能,可是在Vue2中完全廢棄了此功能,若是須要雙向綁定須要本身來實現。
咱們能夠爲組件的 props 指定驗證規格。若是傳入的數據不符合規格,Vue 會發出警告。當組件給其餘人使用時,這頗有用。
要指定驗證規格,須要用 對象的形式,而不能用字符串數組:
Vue.component('example', { props: { // 基礎類型檢測 (`null` 意思是任何類型均可以) propA: Number, // 多種類型 propB: [String, Number], // 必傳且是字符串 propC: { type: String, required: true }, // 數字,有默認值 propD: { type: Number, default: 100 }, // 數組/對象的默認值應當由一個工廠函數返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定義驗證函數 propF: { validator: function (value) { return value > 10 } } } })
type
能夠是下面原生構造器:
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
type
也能夠是一個自定義構造器函數,使用instanceof
檢測。當 prop 驗證失敗,Vue 會在拋出警告 (若是使用的是開發版本)。注意 props 會在組件實例建立以前進行校驗,因此在
default
或validator
函數裏,諸如data
、computed
或methods
等實例屬性還沒法使用。
實例:將第三小節中的demo代碼修改,給傳遞的數據姓名和年齡加上驗證,姓名必須爲字符串類型,年齡必須爲數字且不爲空:
在年齡那輸入「m」,能夠看見控制檯報錯了。由於傳遞的數據是字符串類型,而年齡props驗證要求的是數字。
父組件是使用 props 傳遞數據給子組件,但子組件怎麼跟父組件通訊呢?這個時候 Vue 的自定義事件系統就派得上用場了。
每一個 Vue 實例都實現了事件接口 (Events interface),即:
$on(eventName)
監聽事件$emit(eventName)
觸發事件另外,父組件能夠在使用子組件的地方直接用 v-on
來監聽子組件觸發的事件。不能用 $on
偵聽子組件拋出的事件,而必須在模板裏直接用 v-on
綁定。
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>v-on綁定自定義事件</title> 6 <script type="text/javascript" src="../lib/js/vue.js"></script> 7 </head> 8 <body> 9 <div id="counter-event-example"> 10 <p>{{ total }}</p> 11 <button-counter v-on:add="incrementTotal"></button-counter> 12 </div> 13 14 <script type="text/javascript"> 15 16 Vue.component('button-counter', { 17 template: '<button v-on:click="incrementCounter">{{ counter }}</button>', 18 data: function () { 19 return { 20 counter: 0 21 } 22 }, 23 methods: { 24 incrementCounter: function () { 25 this.counter += 1; 26 this.$emit('add'); //向父組件報告,本身發生了‘add’事件 27 } 28 } 29 }); 30 new Vue({ 31 el: '#counter-event-example', 32 data: { 33 total: 0 34 }, 35 methods: { 36 incrementTotal: function () { 37 this.total += 1 38 } 39 } 40 }) 41 </script> 42 </body> 43 </html>
分析:
第一步:在子組件裏面把點擊事件(click)綁定給了函數 incrementCounter(即圖片裏面的步驟1),這裏容易理解,即點擊了子組件的按鈕將會觸發位於子組件的 incrementCounter函數。
第二步:在觸發 incrementCounter 函數的時候,子組件的數字在原來值的基礎上加1,而且表示向父組件報告本身觸發了 add 事件(至於發生的事件叫什麼名字,能夠隨意取名,只要在父組件中綁定時名稱一致便可。)。
第三步: 在子組件觸發add事件的時候,父組件調用 incrementTotal 函數來響應子組件。
這時咱們回想步驟2,在子組件咱們已經使用emit來進行通知,因此,這樣就造成父子組件間的相互呼應傳遞信息,其實在開發的過程當中父子組件通信也都是使用這樣的方法,父組件傳遞信息給子組件的時候會經過props參數,一般不會直接在子組件中修改父組件傳遞下來的信息,並且經過這樣的一個鉤子去通知父組件對某些參數進行改變。
第四步:定義父組件的 incrementTotal 函數,數值在原基礎上加1。
有時候,你可能想在某個組件的根元素上監聽一個原生事件。可使用 .native
修飾 v-on
。例如:
<my-component v-on:click.native="doTheThing"></my-component>
自定義事件能夠用來建立自定義的表單輸入組件,使用 v-model
來進行數據雙向綁定。看看這個:
<input v-model="something">
這不過是如下示例的語法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value">
因此在組件中使用時,它至關於下面的簡寫:
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>
接受一個 value
屬性因此要讓組件的 v-model
生效,它應該 (在 2.2.0+ 這是可配置的):
value
屬性input
事件demo:
1 <div id="demo"> 2 <currency-input v-model="price"></currency-input> 3 </div> 4 5 <template id="currency"> 6 <span>$<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)" /></span> 7 </template> 8 9 <script type="text/javascript"> 10 Vue.component('currency-input',{ 11 template:'#currency', 12 props: ['value'], 13 methods: { 14 // 不是直接更新值,而是使用此方法來對輸入值進行格式化和位數限制 15 updateValue: function (value) { 16 var formattedValue = value 17 // 刪除兩側的空格符 18 .trim() 19 // 保留 2 小數位 20 .slice(0,value.indexOf('.') === -1 ? value.length: value.indexOf('.') + 3); 21 // 若是值不統一,手動覆蓋以保持一致 22 if (formattedValue !== value) { 23 this.$refs.input.value = formattedValue 24 } 25 // 經過 input 事件發出數值 26 this.$emit('input', Number(formattedValue)) 27 } 28 } 29 }); 30 31 var vm = new Vue({ 32 el:'#demo', 33 data:{ 34 price:'' 35 } 36 }) 37 </script>
(只能輸入小數點後兩位,只能輸入數字)
特殊屬性 ref:
(預期:
string)
ref
被用來給元素或子組件註冊引用信息。引用信息將會註冊在父組件的$refs
對象上。若是在普通的 DOM 元素上使用,引用指向的就是 DOM 元素; 若是用在子組件上,引用就指向組件實例:<!-- vm.$refs.p will be the DOM node --> <p ref="p">hello</p> <!-- vm.$refs.child will be the child comp instance --> <child-comp ref="child"></child-comp>當
v-for
用於元素或組件的時候,引用信息將是包含 DOM 節點或組件實例的數組。關於ref註冊時間的重要說明: 由於ref自己是做爲渲染結果被建立的,在初始渲染的時候你不能訪問它們 - 它們還不存在!
$refs
也不是響應式的,所以你不該該試圖用它在模版中作數據綁定。
(2.2.0新增)
默認狀況下,一個組件的 v-model
會使用 value
屬性和 input
事件,可是諸如單選框、複選框之類的輸入類型可能把 value
屬性用做了別的目的。model
選項能夠迴避這樣的衝突:
Vue.component('my-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean, // this allows using the `value` prop for a different purpose value: String }, // ... })
<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代碼等價於:
<my-checkbox :checked="foo" @change="val => { foo = val }" value="some value"></my-checkbox>
在一些狀況下,咱們可能會須要對一個 prop 進行『雙向綁定』。事實上,這正是 Vue 1.x 中的 .sync
修飾符所提供的功能。當一個子組件改變了一個 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會致使問題,由於它破壞了『單向數據流』的假設。因爲子組件改變 prop 的代碼和普通的狀態改動代碼毫無區別,當光看子組件的代碼時,你徹底不知道它什麼時候悄悄地改變了父組件的狀態。這在 debug 複雜結構的應用時會帶來很高的維護成本。
上面所說的正是咱們在 2.0 中移除 .sync
的理由。可是在 2.0 發佈以後的實際應用中,咱們發現 .sync
仍是有其適用之處,好比在開發可複用的組件庫時。咱們須要作的只是讓子組件改變父組件狀態的代碼更容易被區分。
從 2.3.0 起咱們從新引入了 .sync
修飾符,可是此次它只是做爲一個編譯時的語法糖存在。它會被擴展爲一個自動更新父組件屬性的 v-on
偵聽器。
以下代碼
<comp :foo.sync="bar"></comp>
會被擴展爲:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
當子組件須要更新 foo
的值時,它須要顯式地觸發一個更新事件:
this.$emit('update:foo', newValue)
有時候兩個組件也須要通訊 (非父子關係)。在簡單的場景下,可使用一個空的 Vue 實例做爲中央事件總線,至關於中轉站,能夠用它來傳遞事件和接收事件。
var bus = new Vue() // 觸發組件 A 中的事件 bus.$emit('id-selected', 1) // 在組件 B 建立的鉤子中監聽事件 bus.$on('id-selected', function (id) { // ... })
下面用一個實例來講明:
1 <div id="demo"> 2 <a-component></a-component> 3 <b-component></b-component> 4 </div> 5 6 7 <script type="text/javascript"> 8 Vue.component('a-component',{ 9 template:'<button @click="submit">提交</button>', 10 methods: { 11 submit() { 12 // 事件名字自定義,用不一樣的名字區別事件 13 this.$root.Bus.$emit('eventName', 123) 14 } 15 } 16 }); 17 18 Vue.component('b-component',{ 19 template:'<p>{{message}}</p>', 20 data:function () { 21 return { 22 message:'b子組件' 23 } 24 }, 25 26 // 當前實例建立完成就監聽這個事件 27 created(){ 28 this.$root.Bus.$on('eventName', function (value) { 29 console.log(value); 30 }); 31 }, 32 33 // 在組件銷燬時別忘了解除事件綁定 34 beforeDestroy() { 35 this.$root.Bus.$off('eventName') 36 } 37 }); 38 39 var vm = new Vue({ 40 el:'#demo', 41 data: { 42 // 空的實例放到根組件下,全部的子組件都能調用 43 Bus: new Vue() 44 } 45 }) 46 </script>
本文的Demo和源代碼已放到GitHub https://github.com/zwl-jasmine95/Vue_test
點個讚唄~~┑( ̄▽  ̄)┍