元件基礎


目錄

# #單一根元素


監聽子元件事件


使用事件拋出一個值

在元件上使用v-model

1.gif

透過插槽分發內容動態元件


#解析DOM 範本時的注意事項


基本範例1.jpg

##這裡有一個Vue元件的範例:

// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

元件是可重複使用的Vue 實例,並且有名字:在這個範例中是

<button-counter>
。我們可以在一個透過

new Vue

建立的Vue 根實例中,把這個元件當作自訂元素來使用:

<div id="components-demo">
  <button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })
因為元件是可重複使用的Vue 實例,因此它們與 new Vue 接收相同的選項,例如 datacomputed

watch#、 methods 以及生命週期鉤子等。僅有的例外是像 el

 這樣根實例特有的選項。 #####################元件的複用###############你可以將元件進行任意次數的複用:###
<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>
############注意當點擊按鈕時,每個元件都會各自獨立維護它的 ###count###。因為你每用一次元件,就會有一個它的新實例被創造出來。 ########################data############################## ######當我們定義這個###<button-counter>### 元件時,你可能會發現它的###data### 並不是像這樣直接提供一個物件:###
data: {
  count: 0
}

取而代之的是,一個元件的data 選項必須是一個函數,因此每個實例可以維護一份被傳回物件的獨立的拷貝:

data: function () {
  return {
    count: 0
  }
}

如果Vue 沒有這條規則,點擊一個按鈕就可能會像如下程式碼一樣影響到其它所有實例:

2.gif


元件的組織


通常一個應用會以一個嵌套的元件樹的形式來組織:

1.png

例如,你可能會有頁頭、側邊欄、內容區等元件,每個元件又包含了其它的像導覽連結、博文之類的元件。

為了能在模板中使用,這些元件必須先註冊以便 Vue 能夠識別。這裡有兩種元件的註冊類型:全域註冊局部註冊。至此,我們的元件都只是透過Vue.component 全域註冊的:

Vue.component('my-component-name', {
  // ... options ...
})

全域註冊的元件可以用在其註冊之後的任何(透過new Vue) 新建立的Vue 根實例,也包含其元件樹中的所有子元件的範本中。

到目前為止,關於元件註冊你需要了解的就這些了,如果你閱讀完本頁內容並掌握了它的內容,我們會推薦你再回來把元件註冊讀完。


透過Prop 向子元件傳遞資料


早些時候,我們提到了創建一個博文組件的事情。問題是如果你不能向這個元件傳遞某一篇部落格的標題或內容之類的我們想展示的資料的話,它是沒有辦法使用的。這也正是 prop 的由來。

Prop 是你可以在元件上註冊的一些自訂特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個元件實例的屬性。為了給博文元件傳遞一個標題,我們可以用一個props 選項將其包含在該元件可接受的prop 列表中:

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

一個元件預設可以擁有任意數量的prop,任何值都可以傳遞給任何prop。在上述模板中,你會發現我們能夠在元件實例中存取這個值,就像存取 data 中的值一樣。

一個prop 被註冊之後,你就可以像這樣把資料當作一個自訂特性傳遞進來:

<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>

2.jpg

然而在一個典型的應用中,你可能在data 裡有一個博文的陣列:

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'My journey with Vue' },
      { id: 2, title: 'Blogging with Vue' },
      { id: 3, title: 'Why Vue is so fun' }
    ]
  }
})

並且想要為每篇博文渲染一個元件:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
></blog-post>

如上圖所示,你會發現我們可以使用 v-bind 來動態傳遞 prop。這在你一開始不清楚要渲染的具體內容,例如從一個 API 取得博文清單的時候,是非常有用的。

到目前為止,關於prop 你需要了解的大概就這些了,如果你閱讀完本頁內容並掌握了它的內容,我們會推薦你再回來把prop 讀完。


單一根元素


#當建構一個<blog-post> ; 元件時,你的模板最終會包含的東西遠不止一個標題:

<h3>{{ title }}</h3>

最起碼,你會包含這篇博文的正文:

<h3>{{ title }}</h3>
<div v-html="content"></div>

然而如果你在模板中嘗試這樣寫,Vue 會顯示一個錯誤,並解釋道every component must have a single root element (每個元件必須只有一個根元素)。你可以將模板的內容包裹在一個父元素內,來修復這個問題,例如:

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

看起來當元件變得越來越複雜的時候,我們的博文不只需要標題和內容,還需要發布日期、評論等等。為每個相關的資訊定義一個prop 會變得很麻煩:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"
  v-bind:content="post.content"
  v-bind:publishedAt="post.publishedAt"
  v-bind:comments="post.comments"
></blog-post>

所以是時候重構一下這個<blog-post> 元件了,讓它變成接受一個單獨的post prop:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:post="post"
></blog-post>
Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <div v-html="post.content"></div>
    </div>
  `
})

上述的這個和一些接下來的範例使用了 JavaScript 的模板字串來讓多行的模板更容易讀。它們在 IE 下並沒有被支持,所以如果你需要在不 (經過 Babel 或 TypeScript 之類的工具) 編譯的情況下支援 IE,請使用折行轉義字元取代。

現在,無論何時為 post 物件新增一個新的屬性,它都會自動地在 <blog-post> 內可用。


監聽子元件事件


#在我們發展<blog- post> 元件時,它的一些功能可能會要求我們和父級元件溝通。例如我們可能會引入一個輔助功能來放大博文的字號,同時讓頁面的其它部分保持預設的字號。

在其父元件中,我們可以透過新增一個postFontSize 資料屬性來支援這個功能:

new Vue({
  el: '#blog-posts-events-demo',
  data: {
    posts: [/* ... */],
    postFontSize: 1
  }
})

它可以在範本中用來控制所有博文的字號:

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
  </div>
</div>

現在我們在每篇博文正文之前添加一個按鈕來放大字號:

Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>
        Enlarge text
      </button>
      <div v-html="post.content"></div>
    </div>
  `
})

問題是這個按鈕不會做任何事:

<button>
  Enlarge text
</button>

當點擊這個按鈕時,我們需要告訴父級元件放大所有部落格的文字。幸好 Vue 實例提供了一個自訂事件的系統來解決這個問題。父級元件可以像處理native DOM 事件一樣透過v-on 監聽子元件實例的任意事件:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"
></blog-post>

同時子元件可以透過呼叫內建的# $emit 方法 並傳入事件名稱來觸發一個事件:

<button v-on:click="$emit('enlarge-text')">
  Enlarge text
</button>

有了這個v-on:enlarge-text="postFontSize = 0.1" 監聽器,父級元件就會接收該事件並更新postFontSize 的值。

3.gif


使用事件拋出一個值

有的時候用一個事件來拋出一個特定的值是非常有用的。例如我們可能想要讓 <blog-post> 元件決定它的文字要放大多少。這時可以使用$emit 的第二個參數來提供這個值:

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

然後當在父級元件監聽這個事件的時候,我們可以透過$event 存取到被拋出的這個值:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>

或者,如果這個事件處理函數是一個方法:

<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>

那麼這個值將會作為第一個參數傳入這個方法:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}


在元件上使用v-model

##自訂事件也可以用於建立支援

v-model 的自訂輸入元件。記住:

<input v-model="searchText">

等價於:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>

當用在元件上時,

v-model 則會這樣:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>

為了讓它正常工作,這個元件內的

<input> 必須:

  • #將其

    value 特性綁定到一個名叫value 的prop 上

  • 在其

    input 事件被觸發時,將新的值透過自訂的input 事件拋出

寫成程式碼之後是這樣的:

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})

#現在

v-model 就應該可以在這個元件上完美地工作起來了:

<custom-input v-model="searchText"></custom-input>

到目前為止,關於元件自訂事件你需要了解的大概就這些了,如果你閱讀完本頁內容並掌握了它的內容,我們會推薦你再回來把

自訂事件讀完。


透過插槽分發內容


#和HTML 元素一樣,我們常常需要向一個元件傳遞內容,像這樣:

<alert-box>
  Something bad happened.
</alert-box>

可能會渲染出這樣的東西:

3.jpg

幸好,Vue 自訂的<slot> 元素讓這變得非常簡單:

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})

如你所見,我們只要在需要的地方加入插槽就行了——就這麼簡單!

到目前為止,關於插槽你需要了解的大概就這些了,如果你閱讀完本頁內容並掌握了它的內容,我們會推薦你再回來把插槽讀完。


動態元件


#有的時候,在不同元件之間進行動態切換是非常有用的,例如在一個多標籤的介面裡:

4.gif

#上述內容可以透過Vue 的<component> 元素加一個特殊的is 特性來實現:

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在上述範例中,currentTabComponent 可以包含

  • 已註冊元件的名字,或

  • 一個元件的選項物件

#你可以在這裡查閱並體驗完整的程式碼,或在這個版本了解綁定元件選項對象,而不是已註冊元件名稱的範例。

到目前為止,關於動態元件你需要了解的大概就這些了,如果你閱讀完本頁內容並掌握了它的內容,我們會推薦你再回來把動態和非同步元件讀完。


解析DOM 範本時的注意事項


有些HTML 元素,如<ul><ol><table><select>,對於哪些元素可以出現在其內部是有嚴格限制的。而有些元素,諸如 <li><tr><option>,只能出現在其它某些特定的元素內部。

這會導致我們在使用這些有約束條件的元素時遇到一些問題。例如:

<table>
  <blog-post-row></blog-post-row>
</table>

這個自訂元件 <blog-post-row> 會被作為無效的內容提升到外部,並導致最終渲染結果出錯。幸好這個特殊的is 特性給了我們一個變通的辦法:

<table>
  <tr is="blog-post-row"></tr>
</table>

需要注意的是如果我們從以下來源使用模板的話,這條限制是不存在

到這裡,你需要了解的解析DOM 模板時的注意事項——實際上也是Vue 的全部必要內容,大概就是這些了。恭喜你!接下來還有很多東西要去學習,但首先,我們推薦你先休息一下,試試 Vue,自己隨意做些好玩的東西。

如果你感覺已經掌握了這些知識,我們推薦你再回來把完整的元件指南,包括側邊欄中元件深入章節的所有頁面讀完。