正文
Vue渲染的兩大基礎方式
new 一個Vue的實例
這個我們一般會使用在掛載根節點這一初始化操作上:
new Vue({ el: '#app' })
注冊組件並使用
通過Vue.component()去注冊一個組件,你就可以全局地使用它了,具體體現在每個被new的 Vue
實例/注冊組件, 的template選項屬性或者對應的DOM模板中,去直接使用
注冊組件
全局注冊
例如,放在通過new創建的Vue實例當中:
Vue.component('my-component', { template: '<p>我是被全局注冊的組件</p>' }) /* Vue.component(組件名稱[字符串], 組件對象) */ new Vue({ el: '#app', template: '<my-component></my-component>' })
demo:
又例如,放在另外一個組件中:
Vue.component('my-component', { template: '<p>我是被全局注冊的組件</p>' }) Vue.component('other-component', { template: '<div>我是另一個全局組件:<my-component></my-component></div>' }) new Vue({ el: '#app', template: '<other-component></other-component>' })
局部注冊
const child = { template: '<p>我是局部注冊的組件</p>' } /* 通過components選項屬性進行局部注冊: components: { 組件名稱[字符串]: 組件對象 } */ new Vue({ el: '#app',
template: '<my-component></my-component>', components: { 'my-component': child } })
demo:
通過組件組合(嵌套),構建大型的應用:
const child = { template: '<p>我是child組件</p>' } const father = { template: '<p>我是father組件,我包含了:<child-component></child-component></p>', components: { 'child-component': child } } const grandFather = { template: '<p>我是grandFather組件,我包含了:<father-component></father-component></p>', components: { 'father-component': father } }
new Vue({ el: '#app', template: '<my-component></my-component>', components: { 'my-component': grandFather } })
demo:
通過new創建Vue實例, 全局注冊組件,局部注冊組件三者的使用頻率(場景)
1.new Vue(), 盡管在Vue官方文檔上在相當多的例子中使用到了創建Vue實例這個操作,實際上它的使用可能並沒有你想象的那么平凡,在很多時候,它可能就只在掛載根實例的時候使用到
【這段話給寫react框架的人看】
對 new Vue()做個最簡單的描述!:在使用上類似於ReactDOM.render()...對,就是那個一開始你擼文檔的時候覺得好像很重要,但最后發現在整個APP中就只使用了一次的那個頂層API ....
2.全局注冊組件的使用也不太頻繁,首先來說,如果大量使用全局注冊的話,當然容易產生組件的命名沖突,這就意味着你在構建大型組件的時候,你不應該選擇用全局注冊構建具體的細顆粒度的組件(實際上即使是小型應用也不推薦啦~~~)
那么全局注冊組件會在哪里使用到呢?
2.1 有許多可全局復用的公共UI組件,你可能希望通過Vue.component({ ...})的方式全局注冊它
2.2 可以很簡單地添加第三方UI框架
【對比】大凡使用過一些UI框架的人,都知道一般情況下,使用這些UI組件的方式就是為元素添加類,像這樣:
<div class='UI框架中定義的類名'></div>
而在Vue中,你可以通過直接使用組件名稱去使用,就和react相關的UI框架一樣
3.大多數時候我們通過組件組合的方式構建頁面的時候,運用的是局部注冊,就像上文所提及的那樣
【注意點】
1.注冊組件必須發生在根實例初始化前
2.data是函數!
Vue中的props數據流
【寫給react學習者們看的】這跟react中設計非常類似,連名稱都相同,所以學過react的同學看這里應該會很輕松吧~~
這里要用到Vue的一個選項屬性——props;
通過在注冊組件中聲明需要使用的props,然后通過props中與模板中傳入的對應的屬性名,去取用這傳入的值
例子:
model部分:
Vue.component('my-component', { props: ['name', 'birthTime'], template: '<p>我叫:{{name}} 我出生於:{{birthTime}}</p>', created: function () { console.log('在created鈎子函數中被調用') console.log('我叫:', this.name) console.log('我出生於:', this.birthTime) } }) new Vue({ el: '#app' })
HTML部分:
<div id='app'> <my-component name="彭湖灣" birth-time="1997 -06 - 06"></my-component> <div id='app'>
demo:
你在注冊組件的時候通過props選項聲明了要取用的多個prop:
props: ['name', 'birthTime'],
然后在模板中通過屬性傳值的方式進行數據的注入:
<my-component name="彭湖灣" birth-time="1997 -06 - 06"></my-component>
再然后我們就可以在注冊組件的模板中使用到props選項中聲明的值了:
template: '<p>我叫:{{name}} 我出生於:{{birthTime}}</p>'
這里要注意幾個點:
props取值的方式
1.如果是在注冊組件的模板內部,直接通過prop的名稱取就OK了,例如
template: '<p>我叫:{{name}} 我出生於:{{birthTime}}</p>'
2.如果在注冊組件的其他地方,用this.prop的方式取用,例如
console.log('我叫:', this.name)
props內寫的是駝峰命名法,為什么在HTML(模板)中又用了短橫線命名法?
(camelCased VS kebab-case)
首先我們知道,Vue組件的模板可以放在兩個地方:
1. Vue組件的template選項屬性中,作為模板字符串
2.放在index.html中,作為HTML
這里的問題在於,HTML特性是不區分大小寫的
所以在Vue注冊組件中通用的駝峰命名法,顯然不適用於HTML中的Vue模板,所以
在HTML中寫入props屬性,必須寫短橫線命名法(就是把原來props屬性中的每個prop大寫換成小寫,並且在前面加個“-”)
總結:
1.在template選項屬性中,可以寫駝峰命名法,也可以寫短橫線命名法
2.在HTML(模板)中,只能寫短橫線命名法,不能寫駝峰
下面我就來證明以上兩點:
對1
Vue.component('my-component', { props: ['name', 'birthTime'], template: '<p>我叫:{{name}} 我出生於:{{birthTime}}</p>', created: function () { console.log('在created鈎子函數中被調用') console.log('我叫:', this.name) console.log('我出生於:', this.birthTime) } })
new Vue({ el: '#app', template: '<my-component name="彭湖灣" birthTime="1997 -06 - 06"></my-component>' })
demo:
name和birthTime都正常顯示,這說明在template模板字符串中,是可以寫駝峰的
(請注意到一點:name既符合駝峰寫法也符合短橫線寫法,而birthTime只符合駝峰寫法)
JS部分
Vue.component('my-component', { props: ['name', 'birthTime'], template: '<p>我叫:{{name}} 我出生於:{{birthTime}}</p>', created: function () { console.log('在created鈎子函數中被調用') console.log('我叫:', this.name) console.log('我出生於:', this.birthTime) } }) new Vue({ el: '#app' })
HTML(模板)部分
<div id='app'> <my-component name="彭湖灣" birthTime="1997 -06 - 06"></my-component> </div>
demo:
這里有個有趣的現象:name對應的值可以正常地顯示,但!birthTime不能
這是因為上文提到的:
name既符合駝峰寫法也符合短橫線寫法,而birthTime只符合駝峰寫法,不符合HTML要求的短橫線寫法
使用v-bind的必要性:props不綁定的前提下,只能被作為字符串解析
Vue.component('my-component', { props: ['number'], template: '<p>檢測number的類型</p>', created: function () { console.log(typeof this.number) } }) new Vue({ el: '#app', template: '<my-component number="1"></my-component>' })
demo:
number被檢測為字符串,這表明在不加v-bind綁定的情況下,props接受到的都是字符串,(注:如果被作為javacript,”1“會被解析為Number的1,而” ‘1’ “才會被解析為String的1)
沒錯,僅僅這一點就會讓我們非常為難,所以,我們需要使用v-bind:
當使用v-bind的時候,在模板中props將會被作為javascript解析:
Vue.component('my-component', { props: ['number'], template: '<p>檢測number的類型</p>', created: function () { console.log(typeof this.number) } }) new Vue({ el: '#app', template: '<my-component v-bind:number="1"></my-component>' })
demo:
這可能拓展我們對v-bind的認知:
1.用v-bind一般是為了做數據的動態綁定
2.有時v-bind並不為了實現點1,只是純粹為了讓字符串內的內容被當作JS解析罷了
Vue的自定義事件
自定義事件是我非常喜歡的Vue的一大特性!!! 看文檔的第一眼我就對它情有獨鍾(雖然那一天離現在也就幾天而已的時間。。。)
先展示代碼和demo:
Vue.component('button-counter', { template: '<button v-on:click="increment">{{counter}}</button>', data: function () { return { counter: 0 } }, methods: { increment: function () { this.counter += 1 this.$emit('increment-event') } } }) new Vue({ el: '#app', data: { totalCounter: 0 }, methods: { total_increment: function () { this.totalCounter += 1 } } })
模板HTML部分:
<div id='app'> <button>{{ totalCounter }}</button> </br> <button-counter v-on:increment-event='total_increment'></button-counter> <button-counter v-on:increment-event='total_increment'></button-counter> </div>
demo:
下面兩個按鈕是兩個相同的子組件,並和上面那個按鈕共同組成了父組件。
當點擊任意一個子組件的按鈕,使其加1,都會使得父組件+1,最終:父組件的數值 = 子組件的數值之和
點擊下方左邊button
點擊下方右邊button
自定義事件的原理
通過$emit(event)觸發一個自定義事件
然后通過$on(event,callback) 去執行對應的callback(回調函數)
(兩個event是字符串,且必須名稱相同)
但$on不能在父組件中監聽子組件拋出的事件,所以我們要做到這一點,可以在父組件的模板中使用到子組件的時候,直接用v-on綁定 (和$on作用效果一致) 就像上面那樣:
<button-counter v-on:increment-event='total_increment'></button-counter>
這樣一來,自定義事件的雛形就變得和原生事件一樣了
即使這樣,上面的代碼可能還是有些難理解,我認為比較重要的是這一段:
increment: function () { this.counter += 1 this.$emit('increment-event') }
因為我們對於事件的運用主要是:利用事件和函數綁定,從而在事件觸發的時候能執行相印的函數
所以! 對於自定義事件,我們要解決的問題就是,“這個事件在什么時候被觸發” 在上面的代碼中,觸發事件的時間(執行 this.$emit('increment-event')的時間)
就恰恰是執行this.counter += 1 的時候
自定義事件的作用
對此,我主要從兩點闡述我的觀點:(非官方文檔內容,自己思考的,覺得不對的可以指出):
自定義事件的作用1 ——“重新定義”了事件監聽機制的范圍
MDN是這樣描述DOM事件的:“DOM事件被發送以通知代碼已發生的有趣的事。每個事件都由基於Event接口的一個對象表示”
在我看來:當你使用事件的時候,你可能試圖做這樣一件事情: 在某一個特定的時間節點(或場景)做某個操作,例如調用一個函數。 而定位這個“時間節點”或“場景”的,就是事件。而我們對事件最喜歡做的事情,就是把事件和某個函數給綁定起來
但我們可能一直都忽略了一個認知:我們認知范圍內的事件,好像只有原生事件呀?例如click(點擊),focus(聚焦),keydown(按鍵)
我們認知內的事件,難道只有這些個固定的范圍嗎?點擊是事件,按下鍵盤按鈕是事件。那么,我們能不能人為地定義一個事件呢? 例如上面的,我們通過代碼處理,讓"某個數據增加1"也作為一個事件,從而去觸發一個函數呢?
這,就是自定義事件的目的和魅力
自定義事件的作用2 ——使得父子組件權責明確
就讓我們看一下 父組件的這個模板吧,在這里,我們發現:
1.父組件不知道子組件究竟做了什么(increment事件觸發前的處理),同時也無需關心
2.父組件只要完成它的任務:在increment事件觸發的時候執行對應的函數就足夠了
對子組件反是
所以,從這個角度上說,自定義事件使得父子組件“權責明確”
【注意】官方文檔的示例可能容易制造這樣一種錯覺:自定義事件是以原生事件(如click)為基礎的,但實際上並不是這樣。
雖然自定義事件和原生事件息息相關,但自定義事件並不以原生事件的觸發為基礎的
Slot的使用
當你試圖使用slot的時候,你可能試圖做這樣一件事情:
用父組件動態地控制子組件的顯示的內容
Vue.component('son-component', { template: '<div><slot></slot></div>' }) new Vue({ el: '#app' })
模板HTML:
<div id='app'> <p>這是slot的內容</p> <son-component> <p>你好,我是slot</p> </son-component> </div>
demo:
【寫給react的同學看的】你可以把slot看作是個3.0版本的props.children
通俗的理解:
在父組件模板中使用子組件的時候,如果在子組件里面嵌套了HTML的內容,它會以”props“的方式傳遞給子組件的模板,並被子組件中的slot接受,例如:
【一個不太專業的說法】
<son-component> <p>你好,我是slot</p> </son-component>
等同於
<son-component slot = '<p>你好,我是slot</p>'></son-component>
具名slot
為了使增強slot的用法,使父組件能夠更加靈活地控制子組件,Vue引入了具名slot
通過name屬性,可以把在父組件中控制子組件同時渲染不同的slot
Vue.component('son-component', { template: '<div><slot name="h1"></slot><slot name="button"></slot><slot name="a"></slot></div>' }) new Vue({ el: '#app' })
HTML模板部分:
<div id='app'> <son-component> <h1 slot='h1' >我是標題</h1> <button slot='button'>我是按鈕</button> <a href='#' slot='a'>我是鏈接</a> </son-component> </div>
demo:
【注意事項】
1.實際上,我覺得我這篇文章的語言有些過於羅嗦(其實很為難,因為說多了怕羅嗦——”太長不看“),說少了又怕不能完整地表達自己的意思,這是我權衡后的所做的結果
2.文中很多只為知識點服務,跟實際的項目構建存在很大差異,例如我把很多模板都放在template選項中,而實際上我們會使用Vue單文件組件來實現這些
【完】