緣起
新的一天又開始啦,大家也應該看到我的標題了,是滴,Vue基礎基本就到這里了,咱們回頭看看這一路,如果你都看了,並且都會寫了,那么現在你就可以自己寫一個Demo了,如果再了解一點路由,ajax請求(這里是axios),那么你就可以准備面試前端了,哈哈,當然沒有這么誇張,往后的路還很長,至少咱們基礎都會了。
這里咱們再溫習下之前講了哪些基礎知識:
《十五 ║Vue前篇:了解JS面向對象原理 & 學會嵌套字面量等4種函數定義 & this指向》
《十六 ║Vue前篇:ES6知識詳細說明 & 如何在JS中進行模塊化編程》
《十七 ║Vue基礎:第一次頁面引入Vue.js,了解 Vue實例是如何通過數據驅動來操作DOM的》
《十八 ║Vue基礎: 學習了常用的十大指令並一一舉例,然后了解到了Vue的計算屬性,偵聽器,當然還有過濾器》
《十九 ║Vue基礎: 通過樣式的動態綁定,進一步學習Vue是如何操作DOM的,然后深入了解生命周期的八個階段,以及其中的職能》
一共是五篇,基本已經涵蓋了Vue的基礎知識,今天呢,再說完組件以后,明天就正式開始搭建本地腳手架,終於開始安裝軟件了[ 哭笑 ],我這幾天考慮了一下,在之后的講解中,可能需要兩個小項目的講解,第一個就是我現在自己用到的一個,大家其實也可以看看 http://vue.blog.azlinli.com(買的服務器不好,首次加載慢),也不是啥隱私,這個是我之前練習的時候自己瞎玩的,只有首頁和詳情頁,用的數據就是咱們在之前系列里講到的.net core api,這個可能在本周,或者本周末說到,主要的就是把之前的講解給穿起來,然后再說下如何使用路由 v-router 和 ajax請求——axios,這樣我這個項目就說到這里,然后第二個也是一個博客系統,用的是一套數據,只不過是用到了 Nuxt 框架了,基本結構又發生了變化,項目整體被封裝了,更趨於工程化,至於為什么要用到這個,就是因為它可以解決 MVVM 前后端分離的 SEO 的老大難的問題,大家可以先問問度娘,到時候都會說到滴。好啦,開始今天的基礎篇最后一章節 —— 深入了解下組件。
零、今天要完成天青色的部分
一、組件的基本使用
1、注冊組件
上篇文件我們也說到了,注冊組件的其中一個辦法就是 Vue.component()
方法,先傳入一個自定義組件的名字,然后傳入這個組件的配置。
Vue.component('mycomponent',{ template: `<div>我的組件</div>`, data () { return { message: '老張的哲學' } } })
定義好后,我們就可以在Vue實例所定義的DOM元素內使用它(就是我們在new vue的時候的 el 節點),這里我們的頁腳組件,全局組件,可以在其他地方使用
<div id="app"> <mycomponent></mycomponent> <my-component></my-component> </div> <script> //注意要在vue實例之前去定義,不然渲染頁面的時候,會報錯 // 定義一個名為 footer-vue 的新組件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP備00000000號</a> </p> </div> `, data () { return { message: 'hello world' } } }) var app = new Vue({ el: '#app',//沒錯,vue實例所定義的DOM元素就是這個,超過了這個區域,定義的組件會無效 data: { }, }) </script>
上邊,我們定義的組件是一個全局的組件,也就是說如果我們定義了多個 vue實例,我們都可以使用這一個組件,這就是全局的,
當然,既然有全局的,我們也有局部的(我們對聯系方式定義局部組件,表示只有在當前頁面的app元素內使用):
注意:全局的組件是 component,而 局部的是 components
var app = new Vue({ el: '#app', data: { }, components: { 'my-component': {//這個就是我們局部組件的名字 在頁面內使用 <my-component></my-component> template: ` <ul class ="contact-list non-style-list"> <li> <b class ="twitter">TWITTER</b>: <a href="#">@laozhang</a> </li> <li> <b class ="weibo">微博</b>: <a href="#">@laozhang</a> </li> <li> <b class ="zhihu">知乎</b>: <a href="#" ></a> </li> <li> <b class ="github">GITHUB</b>: <a href="https://github.com/anjoy8">anjoy8</a> </li> <li> <b class ="email">EMAIL</b>: <a href="mailto:laozhang@azlinli.com">randypriv at azlinli</a> </li> </ul> `,
data () {
return {
message: 'hello world two'
}
},
directives:{//自定義局部指令,使用的時候,直接可以 <my-component v-focus><my-component>
focus;{
inserted(el){
el.focus();
}
}
}
}
}
})
2、組件的規范定義——單個根元素 + Data函數化
觀察一下上邊兩種寫法與的特點,大家應該也能說出來:
相同點:組件的模板只能有一個根節點,或者說根標簽只能有一個(第一個的根元素是 <div>,第二個根元素是 <ul>),如果定義成這樣就是不允許的,這里是兩個根元素 <div> 和 <a>:
template: `<div>我的地盤聽我的,哈哈,只能在當前我的Vue實例中使用</div> <a>我的一個標簽</a>
`,
我們看到在定義組件的時候和平時定義的 data 不一樣,這里的定義一定要是一個函數,因為如果像Vue實例那樣,傳入一個對象,由於JS中對象類型的變量實際上保存的是對象的引用
,所以當存在多個這樣的組件時,會共享數據,導致一個組件中數據的改變會引起其他組件數據的改變。而使用一個返回對象的函數,每次使用組件都會創建一個新的對象,這樣就不會出現共享數據的問題來了。
Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p> <a href="#">京ICP備00000000號</a> </p> </div> `, data :{//這是錯誤栗子 message: 'hello world'//我們用普通屬性的方法 } })
如果我們按照一個屬性的寫法的話,頁面會成功的報了一個這樣的錯誤,而且大家注意,這個錯誤是出現在哪里的,沒錯就是掛載結束前,也就是說,和實例化數據沒影響,但是在掛載到頁面,頁面渲染的時候,出現了這個錯誤,所以大家在初學的時候,還是要多了解下生命周期的概念。
注意:因為組件是可復用的 Vue 實例,所以它們與
new Vue
接收相同的選項,例如data
、computed
、watch
、methods
以及生命周期鈎子等。僅有的例外是像el
這樣根實例特有的選項。
3、另外一種注冊方式,通過 全局API:Vue.extend()創建,然后由component來注冊,兩步
// extend 創建組件 var MyComponent = Vue.extend({ template: '<div>A custom component!</div>' }); // component注冊 組件 Vue.component('my-component', MyComponent);//使用到了 extend 創建的組件 var vm = new Vue({ el: '#example', data: { } })
兩種寫法沒有什么太多的區別,基本來說
extend 是構造創建一個組件的語法器,你給它參數 他給你創建一個組件, 然后這個組件,你可以作用到Vue.component 這個全局注冊方法里, 也可以在任意vue模板里使用apple組件
var apple = Vue.extend({
….
})
Vue.component(‘apple’,apple)你可以作用到vue實例或者某個組件中的components屬性中並在內部使用apple組件
new Vue({
components:{
apple:apple
}
})
可見上邊的定義過程比較繁瑣,也可以不用每次都調用兩個,可以直接用 Vue.component 創建 ,也可以取組件 例如下
var apple = Vue.component(‘apple’)
4、模板html 可以單寫出來
<template id="temApp">//這里是定義一個id, <div> <div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/about">Form</router-link> | <router-link to="/Vuex">Vuex</router-link> </div> <router-view/> </div> </template> //在使用的時候,直接引用 #temApp Vue.component('footer-vue', { template:'#temApp', data () { return { message: 'hello world' } } })
5、動態組件
你一定在開發中會遇到這樣的需求,就是一個banner的切換:
我們這時候可以使用動態組件,很容易實現這個需求,通過 Vue 的 <component>
元素加一個特殊的 is
特性來實現:
<!-- 組件會在 `currentTabComponent` 改變時改變 --> <component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent
可以包括
- 已注冊組件的名字,或
- 一個組件的選項對象
<div id="dynamic-component-demo" class="demo"> <button v-for="tab in tabs"//for 循環展示 banner v-bind:key="tab" v-bind:class="['tab-button', { active: currentTab === tab }]" //這里語法錯誤,刪除本行注釋!綁定樣式,當前組件增加 active 樣式 v-on:click="currentTab = tab" >{{ tab }}</button> <!-- 組件的使用 通過currentTabComponent 來動態展示是哪一個組件 --> <component v-bind:is="currentTabComponent" //這里語法錯誤,刪除本行注釋!通過 is 特性,來動態實現組件,核心 class="tab" ></component> </div> //定義三個組件,可以比如是我們的三個頁面, Vue.component('tab-home', { template: '<div>Home component</div>' //組件1,也就是頁面1 }) Vue.component('tab-posts', { template: '<div>Posts component</div>' //組件2,頁面2 }) Vue.component('tab-archive', { template: '<div>Archive component</div>' //組件3,頁面3 }) new Vue({ el: '#dynamic-component-demo', data: { currentTab: 'Home',//當前banner名稱 tabs: ['Home', 'Posts', 'Archive']//定義三個banner }, computed: {//計算屬性,實時監控獲取當然banner的值,並返回到頁面 currentTabComponent: function () { return 'tab-' + this.currentTab.toLowerCase()//組件名稱拼串,一定要和上邊的三個組件名對應 } } })
注意:這里可以使用 <keep-alive> 來緩存當然組件的內容
<keep-alive>
<component :is="currentTabComponent"></component>
<keep-alive>
二、屬性Props —— 父子通訊(父傳子)
在 Vue 中,父子組件的關系可以總結為 prop
向下傳遞,事件
向上傳遞。父組件通過 prop
給子組件下發數據,子組件通過事件
給父組件發送消息,這里咱們先說下向下傳遞,通過屬性Props屬性來完成。
1、使用動態屬性Props,可以將父組件的數據傳遞給子組件,從而達到父子通訊的第一步,舉個栗子
還記得之前咱們說的,Vue 其實就是由許許多多個組件拼接而成,高效復用,相互之間可以傳值,但是又不受影響,最常見的應用就是:組件 A 在它的模板中使用了組件 B。它們之間必然需要相互通信:父組件可能要給子組件下發數據,子組件則可能要將它內部發生的事情告知父組件。大家第一次使用的時候可能會有點兒不舒服,但是使用熟練以后,就會發現真的得心應手,所以咱們就先看看組件是如何通訊的。
首先大家還記得咱們定義的頁腳組件么,就是剛剛說到的。咱們看到最下邊是備案號,現在想在備案號旁邊加上咱的昵稱”老張的哲學“,想想很簡單嘛,想想肯定不能直接寫死數據吧,正好看到頁面內定義vue實例的時候,有這個屬性嘛,直接放到咱們的頁腳組件里,嗯就是這樣:
// 定義一個名為 footer-vue 的新組件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP備00000000號{{authorHtml}}</a> </p> </div> `, data () { return { message: 'hello world' } } })
然后滿懷開心的刷新頁面一看,額報錯了:
然后很熟練的必應翻譯了一下(這是一個反面教材,大家要好好學習英文,多看國外教程 [苦笑] ),得到這樣的:屬性或方法 "authorHtml " 不應該是在實例上定義的, 而是在呈現過程中引用的。通過初始化屬性, 確保此屬性在數據選項或基於類的組件中是被動的。說人話就是,這里不能使用實例上定義的值,好吧,查閱資料發現,組件只能被動的接受父組件的傳參數,嗯於是乎我們這樣寫:
<footer-vue :foo="author"></footer-vue>//在自定義的組件上,新增一個動態屬性,然后屬性的值 author 是父組件的,這個時候父組件就是已經把值給發過去了
這個時候,我們就需要在子組件里接收一下
// 定義一個名為 footer-vue 的新組件 Vue.component('footer-vue', { template: ` <div id="footer-vue"> <p>2018 <a href="#">LZ's Blog</a> - Hosted by <a href="#" style="font-weight: bold">Coding Pages</a></p> <p> <a href="#">京ICP備00000000號{{foo}}</a>//這里根據自身的props的參數來賦值 </p> </div> `, props: ['foo'],//這里根據組件的props屬性,來被動接受組件傳遞來的參數 data () { return { message: 'hello world' } } })
刷新頁面,這時候就真正的成功了。
2、使用靜態Props傳值
這里我們得到的結果和上邊的是一樣的,直接是一個字符串結果,而不是一個變量屬性。
Vue.component('child', { // 聲明 props props: ['message'], // 就像 data 一樣,prop 也可以在模板中使用 // 同樣也可以在 vm 實例中通過 this.message 來使用 template: '<span>{{ message }}</span>' }) <child message="老張的哲學"></child>
上邊咱們可以看到,通過父子傳值,我們把數據傳遞了過去,但是這個時候你需要思考,我們只能傳遞數據 data 么,答案是否定的,我們還可以傳遞方法事件 Function !很簡單,直接看代碼:
// 實例化 Vue var V = new Vue({ el: '#app', data: { now:'', lists:[ {id:1,name:'孫悟空'}, {id:2,name:'豬八戒'}, {id:3,name:'沙和尚'}, {id:4,name:'唐僧'}, {id:5,name:'小白龍'}, ], answer: 'Time is:' }, watch: { }, methods: { run(){ console.log(77) } }, mounted() { //this.now=this.dateFtt("yyyy-MM-dd hh:mm:ss",new Date());; } }) // 調用子組件,傳遞數據 lists 和事件 run <child :lists="lists" :run="run"> <template slot-scope="a"> {{a}} </template> </child> // 定義子組件 Vue.component('child',{ props:['lists','run'], methods:{ testRun(){ this.run() } }, template:` <div @click='testRun'> <ul> <li v-for="list in lists"> <slot :bbbbb="list"></slot> </li> </ul> </div> ` });
然后我們點擊 div,就會觸發父組件事件:
是不是很簡單,父子傳值,方法事件也是特別常用的,大家要多學習學習。
3、注意大小寫的命名的寫法
注意:HTML 特性是不區分大小寫的。所以,當使用的不是字符串模板時,camelCase (駝峰式命名) 的 prop 需要轉換為相對應的 kebab-case (短橫線分隔式命名),比如 如何上邊的foo 寫成了 fooPro ,那我們定義屬性的時候,就應該寫 foo-pro
<footer-vue :foo-pro="author"></footer-vue>
如果我們寫了 fooPro 這樣的寫法,掛載頁面的時候,就會警告,並且不會成功渲染
如果你使用字符串模板,則沒有這些限制。(字符串模板:指的是在組件選項里用 template:"" 指定的模板,換句話說,寫在 js 中的 template:"" 中的就是字符串模板。)
4、通過訂閱發布來實現通訊
這個其實很簡單,使用的也一般,不是很多,需要安裝一個庫 pubsub-js ,然后我們就可以在任何組件內來實現訂閱發布,從而來實現通訊了:
父組件 —— 訂閱消息(綁定事件監聽)
子/孫組件 —— 發布消息(觸發事件)
三、自定義事件 —— 子傳父
我們知道,父組件使用 prop 傳遞數據給子組件。但子組件怎么跟父組件通信呢?這個時候 Vue 的自定義事件系統就派得上用場了。
1、使用 v-on 綁定自定義事件
每個 Vue 實例都實現了事件接口,即:
- 使用 $on(eventName) 監聽事件
- 使用 $emit(eventName) 觸發事件
Vue 的事件系統與瀏覽器的 EventTarget API
有所不同。盡管它們的運行起來類似,但是 $on
和 $emit
並不是addEventListener
和 dispatchEvent
的別名。
另外,父組件可以在使用子組件的地方直接用 v-on
來監聽子組件觸發的事件。
//1、子組件內,有一個click,當點擊的時候 觸發 incrementCounter 方法
//2、方法被觸發以后,向父組件 發送一個信號廣播,並傳遞參數 counter,名字就是 increment。
//3、父組件通過監聽,來獲取到這個廣播信號 increment ,然后觸發 incrementTotal 方法
//4、incrementTotal 被觸發,獲取到參數 counter 值,執行相應的操作
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter>//3、父組件通過監聽,來獲取到這個廣播信號 increment ,然后觸發 incrementTotal 方法 </div> Vue.component('button-counter', { template: '<button v-on:click="incrementCounter">{{ counter }}</button>',//1、子組件內,有一個click,當點擊的時候 觸發 incrementCounter 方法 data: function () { return { counter: 0 } }, methods: { incrementCounter: function () { this.counter += 1 this.$emit('increment',this.counter)//2、方法被觸發以后,向父組件 發送一個信號廣播,並傳遞參數 counter,名字就是 increment。 } },
mounted(){//常用,掛載完成后執行
this.incrementCounter();
} }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal(counter) {//4、incrementTotal 被觸發,獲取到參數 counter 值,執行相應的操作 this.total = counter } } })
上邊注釋的已經很清楚,就好像冒泡一樣的,往上走,和 父傳子,正好相反。
如果你這個看懂了,可以把上一節中的,通過屬性傳遞方法的那種操作給改一下,改成我們的自定義事件的方式:
<custom-input @run="run"></custom-input> <br/> {{something}} Vue.component('custom-input', { template: '<input type="text" @input="updateValue($event.target.value)"/>', methods:{ updateValue:function(value){ this.$emit('run', value) } } }) var vm = new Vue({ el: '#app', data: { something:'' }, methods:{ run(value){ this.something=value } } })
2、使用自定義事件的表單輸入組件
自定義事件可以用來創建自定義的表單輸入組件,使用 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>
<div id="app"> <custom-input v-model="something"></custom-input> <br/> {{something}} </div> // 注冊 Vue.component('custom-input', { props:['something'], template: '<input type="text" v-bind:value="something" v-on:input="updateValue($event.target.value)"/>', methods:{ updateValue:function(value){ this.$emit('input', value) } } }) var vm = new Vue({ el: '#app', data: { something:'' } })
四、使用插槽slot分發內容(完善中)
2019-06-27 更新:
<div id="app"> <child-component></child-component> </div> <script> Vue.component('child-component',{ template:` <div>Hello,World!</div> ` }) let vm = new Vue({ el:'#app', data:{ } }) </script>
Vue.component('child-component',{ template:` <div> <h4>Header.vue</h4> <slot name="girl"></slot> <div style="height:1px;background-color:red;"></div> <slot name="boy"></slot> <div style="height:1px;background-color:red;"></div> <slot></slot> </div> ` })
<child-component>
<template slot="girl">
<p style="color:red;">漂亮、美麗、購物、逛街</p>
</template>
<template slot="boy">
<strong>帥氣、才實</strong>
</template>
<div>
<a href='#' >
我是一個沒有表情的 a 標簽
</a>
</div>
</child-component>
<child-component>
<template slot="boy">
<strong>帥氣、才實</strong>
</template>
<div>
<div >
我這里需要一個div就行
</div>
</div>
</child-component>
Vue.component('child',{ props:['lists'], template:` <div> <ul> <li v-for="list in lists"> <slot :scope="list"></slot> </li> </ul> </div> ` });
var V = new Vue({ el: '#app', data: { lists:[ {id:1,name:'孫悟空'}, {id:2,name:'豬八戒'}, {id:3,name:'沙和尚'}, {id:4,name:'唐僧'}, {id:5,name:'小白龍'}, ] }, })
<div id="app"> <child :lists="lists"> <template slot-scope="a"> <div v-if='a.scope.id==1'>你好:<span>{{a.scope.name}}</span></div> <div v-else>{{a.scope.name}}</div> </template> </child> </div>
2018-09-14 更新:
如果我們定義了一個組件 O,需要在 A、B、C三個頁面使用,在O中有一部分是三個子組件相同的,有一部分是各自的不同的,這個時候我們就可以使用 slot 分發;
內容分發的作用,就是為了組件的靈活性,在一個模板中,可以供調用者自定義部分
//聲明模板 <template id="mysolt"> <div> <h3>我的公共的相同的內容</h3> <slot name="s1"></slot> </div> </template> //定義組件 Vue.component('my-solt', { template:'#mysolt', data () { return { message: 'hello world' } } }) //在a頁面 <body> <div id="app"> <my-solt> <ul slot="s1"> <li>a</li> <li>a</li> <li>a</li> </ul> </my-solt> </div> </body> //在b頁面 <body> <div id="app"> <my-solt> <ul slot="s1"> <p>b</p> <p>b</p> <p>b</p> </ul> </my-solt> </div> </body> //在c頁面 <body> <div id="app"> <my-solt> <ul slot="s1"> <div>ccc</div> </ul> </my-solt> </div> </body>
在使用組件時,我們常常要像這樣組合它們:
<app> <app-header></app-header> <app-footer></app-footer> </app>
注意兩點:
<app>
組件不知道它會收到什么內容。這是由使用<app>
的父組件決定的。<app>
組件很可能有它自己的模板。
為了讓組件可以組合,我們需要一種方式來混合父組件的內容與子組件自己的模板。使用特殊的 <slot>
元素作為原始內容的插槽。
一個常見錯誤是試圖在父組件模板內將一個指令綁定到子組件的屬性/方法:
<!-- 無效 --> <child-component v-show="someChildProperty"></child-component>
正確做法:
Vue.component('child-component', { // 有效,因為是在正確的作用域內 template: '<div v-show="someChildProperty">Child</div>', data: function () { return { someChildProperty: true } } })
1、單個插槽
假定 my-component 組件有如下模板:
<div>
<h2>我是子組件的標題</h2>
<slot>
只有在沒有要分發的內容時才會顯示。
</slot>
</div>
父組件模板:
<div> <h1>我是父組件的標題</h1> <my-component> <p>這是一些初始內容</p> <p>這是更多的初始內容</p> </my-component> </div>
渲染結果:
五、結語
今天簡單的說了下關於組件的一些問題,因為事件的問題,還沒有說完,還在進一步整理當中,大家可以以后進一步的瀏覽本博文,通過組件的學習,大家在Vue開發的道路上又進了一步,好啦,關於Vue的基礎知識就是這么多了,明天開始進入代碼練習啦~~