首發日期:2019-01-26vue
【官方的話】組件系統是 Vue 的另外一個重要概念,由於它是一種抽象,容許咱們使用小型、獨立和一般可複用的組件構建大型應用。仔細想一想,幾乎任意類型的應用界面均可以抽象爲一個組件樹:webpack
小菜鳥的話:定義組件就好像定義了一堆「帶名字」的模板,好比說可能會有叫作「頂部菜單欄」的組件,咱們能夠屢次複用這個「頂部菜單欄」而省去了大量重複的代碼。web
<body> <div id="app"> <my-template></my-template><!-- 利用組件名來使用定義的「模板」 --> <my-template></my-template> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-template',{ // 第一個參數是組件名,第二個參數是模板的內容 template: '<div><span>個人模板</span></div>' }) var vm = new Vue({ el: '#app' }) </script>
代碼效果:
vuex
組件註冊就是「定義模板」,只有註冊了的組件,Vue纔可以瞭解怎麼渲染。npm
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <ol> <!-- 使用組件 --> <todo-item></todo-item> <todo-item></todo-item> <todo-item></todo-item> </ol> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> //全局定義組件,第一個參數是組件名,template的值是組件的內容 Vue.component('todo-item', { template: '<li>這是個待辦項</li>' }) // 實例化是必須的,要把使用組件的區域交給vue管理 var app = new Vue({ el: '#app', }) </script> </html>
全局註冊每每是不夠理想的。好比,若是你使用一個像 webpack 這樣的構建系統,全局註冊全部的組件意味着即使你已經再也不使用一個組件了,它仍然會被包含在你最終的構建結果中。這形成了用戶下載的 JavaScript 的無謂的增長。數組
在這些狀況下,你能夠經過一個普通的 JavaScript 對象來定義組件:瀏覽器
<body> <div id="app"> <my-component></my-component><!-- 利用組件名來使用組件 --> <my-component></my-component> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var ComponentA = { // 定義一個組件 template: '<div><span>個人模板</span></div>' } var vm = new Vue({ el: '#app', components: { // 而後在實例中聲明要使用這個組件,key是在這個實例中的組件名,value是組件 'my-component': ComponentA } }) </script>
上面的全局註冊說了容許在組件中使用其餘組件,但注意局部註冊的組件要聲明使用其餘組件纔可以嵌套其餘組件。例如,若是你但願 ComponentA 在 ComponentB 中可用,則你須要這樣寫:緩存
<body> <div id="app"> <my-component></my-component> <my-component-b></my-component-b> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var ComponentA = { // 1.定義一個組件 template: '<div><span>個人模板</span></div>' } var ComponentB = { // 2.定義一個組件 components: { //3.聲明使用A組件 'my-component': ComponentA }, template: '<my-component></my-component>' // 4.須要在組件的template中寫上另外一個組件 } var vm = new Vue({ el: '#app', components: { 'my-component': ComponentA, 'my-component-b': ComponentB } }) </script>
組件名可使用類my-component-name(kebab-case (短橫線分隔命名))或MyComponentName的格式(PascalCase 首字母大寫命名法),使用組件的時候能夠<my-component-name>
或<MyComponentName>
,但在有些時候首字母大寫命名法定義組件的是不行的,因此一般推薦使用<my-component-name>
【當你使用首字母大寫命名法來定義組件的時候,不能直接在body中直接寫組件名,而要求寫在template中,以下例】。app
<body> <div id="app"> <my-component></my-component> <my-component-demo></my-component-demo> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-component',{ template: '<div><span>個人模板A</span></div>' }) Vue.component('my-component-demo',{ // 這個是用來測試第二種命名法定義組件的,首字母大寫時要寫在字符串模板中才能顯示(否則顯示不了) template: '<MyComponentB></MyComponentB>' }) Vue.component('MyComponentB',{ template: '<div><span>個人模板B</span></div>' }) var vm = new Vue({ el: '#app' }) </script>
每一個組件必須只有一個根元素!!
因此下面是不合法的:
若是你確實要有多個元素,那麼要有一個根元素包裹它們:
組件也是一個實例,因此組件也能夠定義咱們以前在根實例中定義的內容:data,methods,created,components等等。
但一個組件的 data 選項必須是一個函數,所以每一個實例能夠維護一份被返回對象的獨立的拷貝
在一些html元素中只容許某些元素的存在,例如tbody元素中要求有tr,而不能夠有其餘的元素(有的話會被提到外面)。下面是一個元素被提到外面的例子【而ul並無太嚴格,因此咱們在前面的todo-list的例子中可以演示成功】
下圖能夠看的出來div被提到table外面了:
這是爲何呢?目前來講,咱們在頁面中實際上是先通過html渲染再通過vue渲染的(後面項目話後是總體渲染成功再展現的),當html渲染時,它就發現了tr裏面有一個「非法元素」,因此它就把咱們自定義的組件提到了table外面。
解決方案:
使用tr元素,元素裏面有屬性is,is的值是咱們要使用的組件名
<body> <div id="app"> <table> <tr is='mtr'></tr> </table> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('mtr', { template: `<tr><td>123</td></tr>` // 這個成功的檢測要使用檢測元素來看 }); var app = new Vue({ el: '#app' }) </script>
但不會在一下狀況中出錯:
<script type="text/x-template">
在上面定義的組件中使用的數據都是固定的數據,一般咱們都但願模板能根據咱們傳入的數據來顯示。
(子組件的意思是當前組件的直接子組件,在目前的單個html文件爲例時,你能夠僅認爲是當前頁面的非嵌套組件。後面講到多個組件的合做時因爲多個組件之間的嵌套,就造成了組件的父子、祖孫、兄弟關係)
要給子組件傳遞數據主要有兩個步驟
演示代碼以下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"> <ol> <!-- 2.給數據賦值 --> <todo-item todo="第一個值"></todo-item> <todo-item todo="第二個值"></todo-item> <todo-item :todo="msg"></todo-item> <!-- 傳入的數據能夠是父組件實例中定義的數據。注意要用:來綁定,否則不會識別成實例數據,而是一個字符串 --> </ol> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('todo-item', { props: ['todo'], // 1.使用props來定義這個組件支持傳的參數 template: '<li>{{ todo }}</li>' // 3.這裏演示一下在組件中使用這個傳入的參數 }) var app = new Vue({ el: '#app', data: { msg: 'hello world' } }) </script> </html>
代碼效果:很明顯的,咱們的值成功傳給子組件了。
$emit()
能夠有多個參數,第一個參數是觸發的事件的名稱,後面的參數都是隨着這個事件向外拋出的參數。演示代碼以下:
<body> <div id="app" > <my-component-c v-on:change-color="doSomething"></my-component-c> <!-- 2.使用組件的時候監聽一下事件,這裏將會調用父組件的函數來處理事件 --> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('my-component-c', { template: ` <button @click="$emit('change-color','hello')">改變顏色</button> ` // 1.定義組件,在組件內定義一個能觸發click事件的按鈕,onclick事件發生時將會調用emit來向外拋出事件【這裏觸發的事件不能使用駝峯命名法。推薦使用短橫線命名法】 // 【第一個參數是自定義事件名稱,第二個和後續的參數能夠用於向組件拋出值,這些值會做爲事件響應函數的參數】 }) var app = new Vue({ el: '#app', data: { msg: 'hello world' }, methods: { // 3.將會調用下面的函數來處理事件 doSomething: function(value){ // 這裏的value是從子組件中拋出的。 console.log(value) // 打印一下 } } }) </script>
【小tips:上面有多重字符串的使用,普通的雙引號和單引號已經不足以嵌套使用了,在外層可使用反引號` ` `【esc下面那個鍵】來包裹,它也能夠達到字符串包裹的效果,特別的是它支持多行字符串。】
祖孫組件傳數據、兄弟組件傳數據都屬於非父子組件之間的傳值。
使用bus傳輸數據的步驟:
Vue.prototype.bus = new Vue()
this.bus.$emit('change',當前組件的數據)
this.bus.$on('change',一個用於賦值的函數)
下面的代碼是點擊某個組件發生數據變化時,另外一個組件的數據也發生變化:
<body> <div id="app"> <child-a></child-a> <child-a></child-a> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> // 1. 給Vue的原型添加一個bus,bus是一個Vue實例 Vue.prototype.bus = new Vue() Vue.component('child-a', { data: function() { return { content: 0 } }, methods: { // 2.定義一個函數,可以在數據發生變化時調用emit(這裏定義一個點擊事件的響應函數) handleClick: function() { this.content+=1 this.bus.$emit('change',this.content) } }, mounted: function(){ var this_= this // 這裏要有一個這樣的賦值,下面不能直接使用this,由於在函數中this指向的已經不是當前對象了,而用_this保存的纔是當前對象 // 3. 在組件中對bus中觸發的事件進行監聽。(當emit觸發事件時會調用) this.bus.$on('change',function(msg){ // 4.函數中的參數是emit觸發事件帶的參數。 this_.content = msg // 5.修改當前組件中的數據 }) }, template: `<div @click='handleClick'>{{ content }}</div>` }); var app = new Vue({ el: '#app' }) </script>
【有個建議,建議寫屬性名的時候都使用kebab-case (短橫線分隔命名) 命名,由於這個的兼容效果最好】
HTML 中的特性名是大小寫不敏感的,因此瀏覽器會把全部大寫字符解釋爲小寫字符。若是你在props中使用了駝峯命名法,那你在定義屬性的時候須要使用kebab-case (短橫線分隔命名) 命名才能正確傳輸數據【由於短橫線後面的字符能夠識別成大寫,從而可以匹配到】。
若是在屬性中也使用駝峯命名法命名屬性的時候會報這樣的錯:Prop "mycontent" is passed to component
, but the declared prop name is "myContent". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "my-content" instead of "myContent"
<body> <div id="app"> <!--下面這裏會報錯 --> <child myContent='hello-1'></child> <!-- 下面會正常 --> <child my-content='hello-2'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child', { props:['myContent'], template: `<div>{{myContent}}</div>` }); var app = new Vue({ el: '#app' }) </script>
一樣的,若是在組件的template屬性中使用駝峯命名法的屬性,那麼這個限制就不存在了。
有時候須要使用第三方的組件的時候,因此會須要傳輸數據給這個組件來渲染。但如何限制傳入的數據的類型呢?
格式:
props: { // 數據名:數據類型 title: String, likes: Number, ... }
若是傳入的類型不對,那麼會報Invalid prop: type check failed for prop "xxx". Expected String with value "xxx", got Number with value xxx.
的錯誤。
若是容許多種值,能夠定義一個數組:
props: { content: [String,Number] }
咱們也能夠給props中的數據設置默認值,若是使用default設置值,那麼沒有傳某個數據時默認使用這個數據。
props: { content: { type:[String,Number], default:'個人默認值' } }
若是使用default給數組或對象類型的數據賦默認值,那麼要定義成一個函數。
若是要求某個數據必須傳給子組件,那麼能夠爲它設置required。
格式:
props: { content: { type: String, required: true } }
若是沒傳,會報Missing required prop: "xxx"
的錯。
若是想要更精確的校驗,可使用validator,裏面是一個函數,函數的第一個參數就是傳入的值,當函數內返回true時,這個值纔會校驗經過。
如下的代碼是要求傳入的字符串長度大於6位的校驗:
Vue.component('child', { props: { content: { type: String, validator: function(value) { return (value.length > 6) } } }
若是驗證不經過,會報Invalid prop: custom validator check failed for prop "xxx"
的錯。
用下面的代碼來講一個問題:
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button>按鈕</button> ` }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
上面的代碼你會發現點擊了按鈕卻沒有調用函數。
而下面的按鈕按了會打出child。
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button @click='childFunction'>按鈕</button> `, methods: { childFunction: function() { console.log('child') } } }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
下面的代碼是使用了emit來達到一樣效果的代碼:
<html lang="en"> <head> <meta charset="utf-8"> <title>demo</title> </head> <body> <div id="app"> <child @click='myFunction'></child> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ template:` <button @click="$emit('click')">按鈕</button> ` }); var vm = new Vue({ el: '#app', methods: { myFunction: function() { console.log('haha') } } }) </script> </html>
<template>
元素當作不可見的包裹元素,讓成組的元素可以統一被渲染出來。<template>
元素當作不可見的包裹元素<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
插槽也能夠分發多份數據。使用指定的插槽名就能夠獲取指定的插槽數據。
若是沒有數據傳過來,那麼插槽能夠定義一個默認的數據用來顯示。
<body> <div id="app"> <child-a> <template slot-scope='props'> <!--2. slot-scope的屬性值是能夠隨意的,表明做用域插槽的名字 --> <h3>{{props.index}}</h3> <!--3. props.xxx 是傳過來的值的名字,值須要綁定到slot中 --> </template> </child-a> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.prototype.bus = new Vue() Vue.component('child-a', { data: function() { return { list: [2,4,6,8,10] } }, // 1. 綁定屬性,名爲index,那麼做用域插槽就能夠獲取名爲index的數據 template: `<div> <slot v-for='item of list' :index=item></slot> </div>` }); var app = new Vue({ el: '#app' }) </script>
<component :is='組件名'></component>
若是改變了is屬性的值,那麼渲染的組件就會發生變化。下面是上圖的演示代碼:
<body> <div id="app"> <button @click='changeLogin'>切換登陸方式</button> <component :is='loginType'></component> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('userInfoLogin',{ template:` <div> <div>用戶名登錄:<input type="text"></div> <div>請輸入密碼:<input type="password"></div> </div> ` }); Vue.component('phoneLogin',{ template:` <div> <div>請輸入手機號:<input type="text"></div> <div>請輸入驗證碼:<input type="text"></div> </div> ` }) var vm = new Vue({ el: '#app', data: { loginType:'userInfoLogin' }, methods: { changeLogin: function() { this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin'; } } }) </script>
下面以上面的動態組件切換爲例:
若是給上面的登陸組件都加上一個created用來顯示渲染次數的話,咱們就能夠看到是否是每次切換都會從新渲染。
若是加了keep-alive以後再也不重複輸出,那麼就說明組件被緩存了。
<body> <div id="app"> <button @click='changeLogin'>切換登陸方式</button><!-- 2.屢次點擊切換,查看組件渲染狀況 --> <keep-alive> <component :is='loginType'></component> <!-- 4.注意在keep-alive包裹以前和以後的控制檯輸出狀況 --> </keep-alive> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('userInfoLogin',{ template:` <div> <div>用戶名登錄:<input type="text"></div> <div>請輸入密碼:<input type="password"></div> </div> `, created: function(){ console.log('userInfoLogin')// 1.用來講明渲染了userInfoLogin組件,屢次渲染則屢次輸出 } }); Vue.component('phoneLogin',{ template:` <div> <div>請輸入手機號:<input type="text"></div> <div>請輸入驗證碼:<input type="text"></div> </div> `, created: function(){ console.log('phoneLogin') // 2.用來講明渲染了phoneLogin組件,屢次渲染則屢次輸出 } }) var vm = new Vue({ el: '#app', data: { loginType:'userInfoLogin' }, methods: { changeLogin: function() { this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin'; } } }) </script>
1.在元素中使用ref屬性給元素起一個有標識意義的名字。
2.this.\(refs能夠獲取當前組件實例的全部使用了ref的元素,`this.\)refs.名字`表明指定的元素。
3.而後你能夠進行dom操做了。
<body> <div id="app"> <div ref='demo'>1</div> <!-- 1. 設置ref --> <button @click='changeColor'>+1</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', methods: { changeColor:function() { console.log(this.$refs.demo.innerText) // 獲取ref對象,有了dom對象你就能夠進行dom操做了。 this.$refs.demo.style = 'color:red' this.$refs.demo.innerText+=this.$refs.demo.innerText } } }) </script>
refs也能夠用在組件上,能夠獲取組件的數據(但這時候的ref獲取的不是一個dom對象,而是一個vue實例對象,因此不能獲取innerText這類dom屬性)。
<body> <div id="app"> <child ref='demo'></child> <button @click='changeColor'>+1</button> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child',{ data: function(){ return { msg:'hello world!' } }, template:'<div>hello</div>' }) var app = new Vue({ el: '#app', methods: { changeColor:function() { console.log(this.$refs.demo.msg) // 獲取組件實例的數據。 } } }) </script>
若是說前面講的都是基礎,必需要掌握的話,那麼動畫效果是錦上添花,無關緊要(對於我這些不追求動畫效果就不顯得重要了),因此這裏就不講了,這裏僅僅是爲了顯示vue能有實現動畫效果的功能。
有興趣的能夠看一下官網:
vue官網動畫效果直通車