Component組件是Vue.js最強大的功能之一,它能夠擴展HTML元素,封裝可重用的代碼(減小代碼冗餘)。
在較高層面上,組件是自定義元素。好比說,咱們本身發明了標籤,別人不承認,可是vue可以幫咱們去解析,幫咱們把它轉換成你們都能承認的。javascript
咱們開發組件的時候,須要告訴vue,咱們建立了一個新的組件,這裏咱們就須要註冊。
註冊分爲局部註冊和全局註冊。html
就是說咱們能夠註冊一個組件,把它掛載到vue的全局變量上。
它裏面有個屬性叫Vue.component(組件名稱,組件定義)。
咱們能夠簡單的來測試一下:vue
<body> <div class="container"> <!-- 引用自定義組件 --> <sf-line></sf-line> <!-- 若是咱們要寫5個這樣的組件,那麼咱們能夠用v-for --> <sf-line v-for="n in 5"></sf-line> <!-- 若是咱們想要在sf-line標籤中直接插入值,能夠用slot插槽 --> <sf-line>你好</sf-line> </div> <script> // 在建立vue對象以前,註冊組件 // 經過全局註冊Vue.component(組件名稱,組件定義),調用組件 // 組件名稱是自定義的 // 組件定義template(模板),封裝html標籤,它裏面是多個html的綜合體,以反引號包裹html內容,(反引號的好處是能夠任意回車,不用作字符串的拼接) Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em"> <span>hello</span> <!-- slot 插槽,能夠用來接受sf-line中的值--> <span><slot></slot></span> </div> ` }); //建立vue的實例 new Vue({ el:'.container', data:{ } }); </script> </body>
這就是一個最簡單的全局註冊的組件。java
如今,咱們不但願將<span>hello</span>中的值寫死,咱們但願可以動態改變span標籤中的值,即外部組件(咱們能夠把.container理解爲一個根組件)向內部組件傳值。此時咱們能夠借用一個屬性props。
props表明屬性,也就是說,外部能夠傳參數,咱們經過props來接收。node
<body> <div class="container"> <!-- 動態綁定屬性 屬性的綁定用v-bind 咱們綁定一個在props中定義的屬性名, 它的屬性值爲vue實例data中,定義的lineText,而且lineText會被看成變量來解析 --> <sf-line v-bind:node="lineText"></sf-line> <!-- 簡寫 --> <sf-line :node="lineText"></sf-line> <!-- 咱們也能夠定義一個屬性,不用v-bind --> <sf-line node="組件"></sf-line> <!-- 若是咱們這樣寫,lineText會被直接看成一個值打印出來,不會被看成變量解析 --> <sf-line node="lineText"></sf-line> </div> <script> //註冊組件 Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em"> <!--接收到props中的值以後,經過雙大括號來取出,雙大括號中的值會被看成變量來解析--> <span>{{node}}</span> </div> `, // 接收參數,假如參數爲node props:['node'] }); //建立vue的實例 new Vue({ el:'.container', // 在.container中,咱們調用了一個子組件<sf-line>,如今咱們能夠在vue實例的data中,定義一個值,好比說lineText data:{ lineText:'component' } }); </script> </body>
這裏咱們要補充一下外部組件與內部組件傳值的機制,這裏咱們要記住:
組件是徹底獨立的,它有本身的data,methods,鉤子函數等,而且這些內容都包含在組件定義中——Vue.component(組件名稱,組件定義)。函數
<body> <div class="container"> <!-- 這是vue實例中的msg --> <p>{{msg}}</p> <!-- 這裏顯示組件中的msg --> <sf-line></sf-line> </div> <script> // 註冊組件,組件必定要寫在vue實例上面 Vue.component('sf-line',{ template:` <div> <!-- 組件是徹底獨立的,因此組件中的msg調用的就是組件data中的msg --> <span>{{msg}}</span> </div> `, // 用來接收外部組件向內部組件傳值 props:[ ], // 組件樣式中,一樣有data,methods,鉤子函數等... // 組件中的data是一個方法(不是屬性),要有返回值return // data方法會返回一個對象,保證它是多實例的 // 若是像vue實例中data:{}這樣寫,那麼它就是單例的,它會出現一個問題,好比說:組件會應用屢次,若是改變其中一個組件,那麼其餘組件也會變,這就是單例的 // 咱們但願這個組件是徹底獨立的,因此咱們要return{...},也就是說:咱們每次調用data的時候,都會新建一個對象 data(){ return{ msg:'這是組件中的msg' } }, methods:{ }, created(){ console.log('created'); } }); //建立vue的實例 new Vue({ el:'.container', data:{ msg:'這是vue實例中的msg' } }); </script> </body>
瞭解了組件傳值以後,咱們來學習一下如何來給組件綁定事件。
咱們根據下面代碼來學習:學習
<body> <div class="container"> <sf-line :node="lineText"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <div style="background-color:orange;border-radius:3px;padding:.5em 1em;margin:.5em;position:relative"> <span>{{node}}</span> <!-- 目標:當點擊關閉時,div消失 --> <!-- 第一種方式:在父元素的角度上,將它隱藏 --> <!-- 第二種方式:子元素本身將本身隱藏 --> <!-- 父組件:.container ; 子組件:<sf-line> --> <span style="position:absolute;right:1em;cursor:pointer">關閉</span> </div> `, props:['node'], methods:{ } }); new Vue({ el:'.container', data:{ lineText:'component' } }); </script> </body>
這裏咱們主要講解
第一種方式:在父元素的角度上,將它隱藏測試
<body> <div class="container"> <sf-line :node="lineText"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <!-- 咱們在div中添加一個v-show,將隱藏操做放在這上面,在組件data中定義它是否顯示 --> <div v-show="isShow" style=" background-color:orange; border-radius:3px; padding:.5em 1em; margin:.5em; position:relative" > <span>{{node}}</span> <!-- 點擊下面按鈕關閉 因爲close是組件內部函數,因此在組件的methods中定義屬性 --> <span @click="close" style=" position:absolute; right:1em; cursor:pointer" >關閉</span> </div> `, props:['node'], data(){ return{ // 默認狀況下讓它顯示 isShow:true } }, methods:{ close(){ // 當點擊close時,讓它隱藏 this.isShow=false; } } }); new Vue({ el:'.container', data:{ lineText:'component' }, methods:{ } }); </script> </body>
此刻咱們點擊的是外部<sf-line>標籤的事件,是在component裏面,可是外部vue實例中的methods並無定義方法。也就是說,子組件本身偷偷摸摸就沒了,可是父組件根本就不知道。
若是說,父組件這時候想要在子組件被刪除以後,從新刷新整個頁面。即子組件要通知外面的父組件,它點擊了按鈕。那麼咱們如今應該怎麼作?
這裏有一個知識點,子組件向父組件傳遞事件,相似於一種事件傳遞,但此處叫事件發射。this
這裏代碼能夠這樣完善:spa
<body> <div class="container"> <!-- 父組件此時要接收子組件發射的自定義事件 v-on此時監聽的是子組件發射的事件close, 咱們爲它在父組件中綁定一個方法closeHandler --> <sf-line :node="lineText" @close="closeHandler"></sf-line> </div> <script> Vue.component('sf-line',{ template:` <!-- 咱們在div中添加一個v-show,將隱藏操做放在這上面,在組件data中定義它是否顯示 --> <div v-show="isShow" style=" background-color:orange; border-radius:3px; padding:.5em 1em; margin:.5em; position:relative" > <span>{{node}}</span> <!-- 點擊下面按鈕關閉 因爲close是組件內部函數,因此在組件的methods中定義屬性 --> <span @click="close" style=" position:absolute; right:1em; cursor:pointer" >關閉</span> </div> `, props:['node'], data(){ return{ // 默認狀況下讓它顯示 isShow:true } }, methods:{ close(){ // 通知父元素 //(子組件向父組件發射emit自定義事件close) this.$emit('close'); // 當點擊close時,讓它隱藏 this.isShow=false; } } }); new Vue({ el:'.container', data:{ lineText:'component' }, methods:{ // 父組件中綁定的方法 closeHandler(){ alert('closeHandler'); } } }); </script> </body>
局部註冊沒必要將每一個組件都註冊到全局。咱們能夠經過某個Vue實例或組件的實例的選項components註冊在,僅在其做用域中可用的組件上。
<body> <div class="container"> <sf-list :node="msg"></sf-list> </div> <script type="text/javascript"> //我聲明的組件 let sfList={ template:` <div>{{node}}</div> `, props:['node'] }; new Vue({ el:'.container', data:{ msg:'hello component' }, // 局部註冊 components:{ // 能夠直接將組件定義寫在這裏面 // 可是,爲了避免使代碼顯得臃腫,我在上面聲明一個組件sfList // 將上面註冊的組件聲明進來 // 屬性值:自定義的組件名 // 屬性值:上面聲明的組件名 'sf-list':sfList /* sfList:sfList 這裏也能夠用駝峯式命名 */ /* sfList 直接這樣簡寫也是能夠的 */ //以上三種方式:<sf-list :node="msg"></sf-list>均可以解析 } }); </script> </body>
插槽定義在組件中,用於接受組件內部的內容。
在全局註冊的第一個例子裏面咱們簡單的用了slot插槽。
這種直接插入,不須要命名的插槽,咱們稱爲匿名插槽。
咱們先來簡單回顧一下:
<body> <div class="container"> <!-- 在組件中,用slot接收插入的內容 --> <sf-container>hello</sf-container> </div> <script type="text/javascript"> //註冊組件 Vue.component('sf-container', template` <div class="sf-container"> <div class="sf-header"> <!-- 用slot接收插入的內容 --> <slot></slot> </div> <div class="sf-content"></div> <div class="sf-footer"></div> </div> `, ); </script> </body>
在一個模板中,能夠出現不少插槽。
咱們要去區分它們,咱們能夠給它取個名字,叫作具名插槽。
<body> <div class="container"> <sf-container> <!-- 此時的屬性值與slot中命名的名字相同 --> <div slot="header">header</div> <div slot="content">content</div> <div slot="footer">footer</div> </sf-container> </div> <script type="text/javascript"> //註冊組件 Vue.component('sf-container', template` <div class="sf-container"> <div class="sf-header"> <!-- 爲不一樣的插槽取不一樣的名字 --> <slot name="header"></slot> </div> <div class="sf-content"> <slot name="content"></slot> </div> <div class="sf-footer"> <slot name="footer"></slot> </div> </div> `, ); </script> </body>
組件中除了具名插槽外,還有一種插槽,就是做用域插槽。
<body> <div class="container"> <!-- 調用一個組件,裏面綁定屬性stuList,將studentList中的值傳給stuList --> <sf-list :stulist="studentList"> <!-- 提供一個模板,它裏面可讓咱們定義td的具體格式 --> <!-- slot-scope用於將元素或組件表示爲做用域插槽,此屬性不支持動態綁定 --> <!-- 屬性名自定義 --> <template slot-scope="stuScope"> <!-- 在這裏顯示插入的內容 --> <!-- 咱們能夠在這裏選擇要插入的數據 --> <!-- 做用域插槽,至關於爲咱們多了一個定製的功能 --> {{stuScope.foo.id}} <a href="#">{{stuScope.foo.name}}</a> </template> </sf-list> </div> <script type="text/javascript"> //註冊組件 Vue.component('sf-list',{ template:` <div> <p>原來的寫法</p> <ul> <li v-for="item in stulist">{{item.name}}</li> </ul> <p>做用域插槽</p> <ul> <li v-for="item in stulist"> <!-- 定義一個插槽,在裏面隨意綁定一個值foo--> <slot :foo="item"></slot> </li> </ul> </div> `, // 接收studentList中的內容(外部組件向內部組件傳值) // props接收的值不區分大小寫,不要用駝峯式命名,不然可能報錯 props:['stulist'] }); //建立實例 new Vue({ el:'.container', data:{ studentList:[{ id:1, name:'terry' },{ id:2, name:'larry' },{ id:3, name:'tom' }] } }); </script> </body>