1. 程式人生 > >【Vue】詳解Vue組件系統

【Vue】詳解Vue組件系統

最終 文件 type html中 emit 監聽 做了 駝峰命名 操作

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單文件組件來實現這些

【完】

技術分享

【Vue】詳解Vue組件系統