Vue基礎:vue組件基礎

基本示例

<div id="components-demo">
    <button-counter></button-counter>
  </div>
  <script>
  // 定義一個名爲 button-counter 的新組件
   Vue.component('button-counter', {
     data: function () {
       return {
         count: 0
       }
     },
     template: '<button v-on:click="count++">點擊了 {{ count }} 次.</button>'
   })
   new Vue({ el: '#components-demo' })
  </script>

在這裏插入圖片描述因爲組件是可複用的 Vue 實例,所以它們與 new Vue 接收相同的選項,例如 datacomputedwatchmethods 以及生命週期鉤子等。僅有的例外是像 el 這樣根實例特有的選項。

組件的複用

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

在這裏插入圖片描述

注意:當點擊按鈕時,每個組件都會各自獨立維護它的 count。因爲你每用一次組件,就會有一個它的新實例被創建。
在這裏插入圖片描述

data 必須是一個函數

一個組件的 data 選項必須是一個函數,因此每個實例可以維護一份被返回對象的獨立的拷貝:

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

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

組件的組織

通常一個應用會以一棵嵌套的組件樹的形式來組織:
在這裏插入圖片描述比如說,你可能會有頁頭側邊欄內容區等組件,每個組件又包含了其它的像導航鏈接博文之類的組件。

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

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

全局註冊的組件可以用在其被註冊之後的任何 (通過 new Vue) 新創建的 Vue 根實例,也包括其組件樹中的所有子組件的模板中

通過Prop向子組件傳遞數據

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

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

一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop。在上述模板中,會發現在組件實例中訪問這個值,就像訪問 data 中的值一樣。

比如1:一個 prop 被註冊之後,你就可以像這樣把數據作爲一個自定義 attribute 傳遞進來:

<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>

栗子展示:

<div id="blog-post">
    <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>
  </div>
  <script>
   Vue.component('blog-post', {
     props: ['title'],
     template: '<h3>{{ title }}</h3>'
   })
   new Vue({ el: '#blog-post' })
  </script>

在這裏插入圖片描述
比如2:然而在一個典型的應用中,你可能在 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>

栗子展示

<div id="blog-post">
    <blog-post 
   v-for="post in posts"
   v-bind:key="post.id" 
   v-bind:title="post.title">
     </blog-post>
  </div>
  <script>
   Vue.component('blog-post', {
     props: ['title'],
     template: '<h3>{{ title }}</h3>'
   })
   new Vue({
     el: '#blog-post',
     data: {
       posts: [
         { id: 1, title: 'My journey with Vue' },
         { id: 2, title: 'Blogging with Vue' },
         { id: 3, title: 'Why Vue is so fun' }
       ]
     }
   })
  </script>

在這裏插入圖片描述

可以使用 v-bind 來動態傳遞 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>
  `
})

監聽子組件事件

在開發 <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>
  `
})

當點擊這個按鈕時,需要告訴父級組件放大所有博文的文本。幸好 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>

栗子展示

<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"
     v-on:enlarge-text="postFontSize += 0.1"
      ></blog-post>
    </div>
  </div>
  <script>
   Vue.component('blog-post', {
     props: ['post'],
     template: `
       <div class="blog-post">
         <h3>{{ post.title }}</h3>
      <button v-on:click="$emit('enlarge-text')">
           Enlarge text
         </button>
         <div v-html="post.content"></div>
       </div>
     `
   })
   new Vue({
     el: '#blog-posts-events-demo',
     data: {
       posts: [
             { id: 1, title: 'My journey with Vue',content:'...content' },
             { id: 2, title: 'Blogging with Vue' ,content:'...content' },
             { id: 3, title: 'Why Vue is so fun',content:'...content' },
           ],
    
       postFontSize: 1
     }
   })
  </script>

在這裏插入圖片描述注意:此有缺陷(點擊一個按鈕,所有標題都會變大)

(1)使用事件拋出一個值

有的時候用一個事件來拋出一個特定的值是非常有用的。例如我們可能想讓 <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
  }
}

栗子展示

<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"
     v-on:enlarge-text="postFontSize += $event"
      ></blog-post>
    </div>
  </div>
  <script>
   Vue.component('blog-post', {
     props: ['post'],
     template: `
       <div class="blog-post">
         <h3>{{ post.title }}</h3>
      <button v-on:click="$emit('enlarge-text', 0.1)">
        Enlarge text
      </button>
         <div v-html="post.content"></div>
       </div>
     `
   })
   new Vue({
     el: '#blog-posts-events-demo',
     data: {
       posts: [
             { id: 1, title: 'My journey with Vue',content:'...content' },
             { id: 2, title: 'Blogging with Vue' ,content:'...content' },
             { id: 3, title: 'Why Vue is so fun',content:'...content' },
           ],    
       postFontSize: 1
     },
     methods: {
       onEnlargeText: function (enlargeAmount) {
         this.postFontSize += enlargeAmount
       }
     }
   })
  </script>

在這裏插入圖片描述

(2)在組件上使用 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 attribute 綁定到一個名叫 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>

栗子展示

<div id="custom-input">
      <custom-input
        v-bind:value="searchText"
        v-on:input="searchText = $event"
      >
   </custom-input>
    </div>
  </div>
  <script>
   Vue.component('custom-input', {
     props: ['value'],
     template: `
       <input
         v-bind:value="value"
         v-on:input="$emit('input', $event.target.value)"
       >
     `
   })
   new Vue({
     el: '#custom-input',
     data: {   
      searchText:'hello'
     }     
   })
  </script>

在這裏插入圖片描述

通過插槽分發內容

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

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

Vue 自定義的 <slot> 元素讓這變得非常簡單:

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

栗子展示

<style type="text/css">
   .demo-alert-box{
    background: palevioletred;
    padding: 15px;
   }
  </style>
<div id="alert-box">
     <alert-box>
       Something bad happened.
     </alert-box>
    </div>
  </div>
  <script>
   Vue.component('alert-box', {
     template: `
       <div class="demo-alert-box">
         <strong>Error!</strong>
         <slot></slot>
       </div>
     `
   })
   new Vue({
     el: '#alert-box'   
   })
  </script>

在這裏插入圖片描述

動態組件

在不同組件之間進行動態切換是非常有用的,比如在一個多標籤的界面裏。

通過 Vue 的 <component> 元素加一個特殊的 is attribute 來實現:

<!-- 組件會在 `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 attribute 給了我們一個變通的辦法:

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

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

  • 字符串 (例如:template: '...')
  • 單文件組件 (.vue)
  • <script type="text/x-template">
發佈了132 篇原創文章 · 獲贊 26 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章