Vue 3.0 組件基礎(chǔ)

2021-07-16 11:28 更新

基本實(shí)例

這里有一個(gè) Vue 組件的示例:

// 創(chuàng)建一個(gè)Vue 應(yīng)用
const app = Vue.createApp({})


// 定義一個(gè)名為 button-counter 的新全局組件
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
})

INFO

在這里演示的是一個(gè)簡(jiǎn)單的示例,但是在典型的 Vue 應(yīng)用程序中,我們使用單個(gè)文件組件而不是字符串模板。你可以在本節(jié)找到有關(guān)它們的更多信息。

組件是可復(fù)用的組件實(shí)例,且?guī)в幸粋€(gè)名字:在這個(gè)例子中是 <button-counter>。我們可以在一個(gè)通過(guò) new Vue 創(chuàng)建的 Vue 根實(shí)例中,把這個(gè)組件作為自定義元素來(lái)使用:

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

app.mount('#components-demo')

點(diǎn)擊此處實(shí)現(xiàn)

因?yàn)榻M件是可復(fù)用的組件實(shí)例,所以它們與 new Vue 接收相同的選項(xiàng),例如 datacomputed、watch、methods 以及生命周期鉤子等。僅有的例外是像 el 這樣根實(shí)例特有的選項(xiàng)。

#組件的復(fù)用

你可以將組件進(jìn)行任意次數(shù)的復(fù)用:

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

點(diǎn)擊此處實(shí)現(xiàn)

注意當(dāng)點(diǎn)擊按鈕時(shí),每個(gè)組件都會(huì)各自獨(dú)立維護(hù)它的 count。因?yàn)槟忝坑靡淮谓M件,就會(huì)有一個(gè)它的新實(shí)例被創(chuàng)建。

#組件的組織

通常一個(gè)應(yīng)用會(huì)以一棵嵌套的組件樹的形式來(lái)組織

例如,你可能會(huì)有頁(yè)頭、側(cè)邊欄、內(nèi)容區(qū)等組件,每個(gè)組件又包含了其它的像導(dǎo)航鏈接、博文之類的組件。

為了能在模板中使用,這些組件必須先注冊(cè)以便 Vue 能夠識(shí)別。這里有兩種組件的注冊(cè)類型:全局注冊(cè)局部注冊(cè)。至此,我們的組件都只是通過(guò) component 全局注冊(cè)的:

const app = Vue.createApp({})


app.component('my-component-name', {
  // ... 選項(xiàng) ...
})

全局注冊(cè)的組件可以在隨后創(chuàng)建的 app 實(shí)例模板中使用,也包括根實(shí)例組件樹中的所有子組件的模板中。

到目前為止,關(guān)于組件注冊(cè)你需要了解的就這些了,如果你閱讀完本頁(yè)內(nèi)容并掌握了它的內(nèi)容,我們會(huì)推薦你再回來(lái)把組件注冊(cè)讀完。

#通過(guò) Prop 向子組件傳遞數(shù)據(jù)

早些時(shí)候,我們提到了創(chuàng)建一個(gè)博文組件的事情。問(wèn)題是如果你不能向這個(gè)組件傳遞某一篇博文的標(biāo)題或內(nèi)容之類的我們想展示的數(shù)據(jù)的話,它是沒(méi)有辦法使用的。這也正是 prop 的由來(lái)。

Prop 是你可以在組件上注冊(cè)的一些自定義 attribute。當(dāng)一個(gè)值傳遞給一個(gè) prop attribute 的時(shí)候,它就變成了那個(gè)組件實(shí)例的一個(gè) property。為了給博文組件傳遞一個(gè)標(biāo)題,我們可以用一個(gè) props 選項(xiàng)將其包含在該組件可接受的 prop 列表中:

const app = Vue.createApp({})


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


app.mount('#blog-post-demo')

一個(gè)組件默認(rèn)可以擁有任意數(shù)量的 prop,任何值都可以傳遞給任何 prop。在上述模板中,你會(huì)發(fā)現(xiàn)我們能夠在組件實(shí)例中訪問(wèn)這個(gè)值,就像訪問(wèn) data 中的值一樣。

一個(gè) prop 被注冊(cè)之后,你就可以像這樣把數(shù)據(jù)作為一個(gè)自定義 attribute 傳遞進(jìn)來(lái):

<div id="blog-post-demo" class="demo">
  <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>

點(diǎn)擊此處實(shí)現(xiàn)

然而在一個(gè)典型的應(yīng)用中,你可能在 data 里有一個(gè)博文的數(shù)組:

const App = {
  data() {
    return {
      posts: [
        { id: 1, title: 'My journey with Vue' },
        { id: 2, title: 'Blogging with Vue' },
        { id: 3, title: 'Why Vue is so fun' }
      ]
    }
  }
}


const app = Vue.createApp({})


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


app.mount('#blog-posts-demo')

并想要為每篇博文渲染一個(gè)組件:

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

如上所示,你會(huì)發(fā)現(xiàn)我們可以使用 v-bind 來(lái)動(dòng)態(tài)傳遞 prop。這在你一開始不清楚要渲染的具體內(nèi)容,是非常有用的。

到目前為止,關(guān)于 prop 你需要了解的大概就這些了,如果你閱讀完本頁(yè)內(nèi)容并掌握了它的內(nèi)容,我們會(huì)推薦你再回來(lái)把 prop 讀完。

#監(jiān)聽子組件事件

在我們開發(fā) <blog-post> 組件時(shí),它的一些功能可能要求我們和父級(jí)組件進(jìn)行溝通。例如我們可能會(huì)引入一個(gè)輔助功能來(lái)放大博文的字號(hào),同時(shí)讓頁(yè)面的其它部分保持默認(rèn)的字號(hào)。

在其父組件中,我們可以通過(guò)添加一個(gè) postFontSize 數(shù)據(jù) property 來(lái)支持這個(gè)功能:

const App = {
  data() {
    return {
      posts: [
        /* ... */
      ],
      postFontSize: 1
    }
  }
}

它可以在模板中用來(lái)控制所有博文的字號(hào):

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

現(xiàn)在我們?cè)诿科┪恼闹疤砑右粋€(gè)按鈕來(lái)放大字號(hào):

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

問(wèn)題是這個(gè)按鈕不會(huì)做任何事:

<button>
  Enlarge text
</button>

當(dāng)點(diǎn)擊這個(gè)按鈕時(shí),我們需要告訴父級(jí)組件放大所有博文的文本。幸好組件實(shí)例提供了一個(gè)自定義事件的系統(tǒng)來(lái)解決這個(gè)問(wèn)題。父級(jí)組件可以像處理 native DOM 事件一樣通過(guò) v-on@ 監(jiān)聽子組件實(shí)例的任意事件:

<blog-post ... @enlarge-text="postFontSize += 0.1"></blog-post>

同時(shí)子組件可以通過(guò)調(diào)用內(nèi)建的 $emit 方法并傳入事件名稱來(lái)觸發(fā)一個(gè)事件:

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

多虧了 @enlarge-text="postFontSize += 0.1" 監(jiān)聽器,父級(jí)將接收事件并更新 postFontSize 值。 點(diǎn)擊此處實(shí)現(xiàn)

我們可以在組件的 emits 選項(xiàng)中列出已拋出的事件。

app.component('blog-post', {
  props: ['title'],
  emits: ['enlarge-text']
})

這將允許你檢查組件拋出的所有事件,還可以選擇 validate them

#使用事件拋出一個(gè)值

有的時(shí)候用一個(gè)事件來(lái)拋出一個(gè)特定的值是非常有用的。例如我們可能想讓 <blog-post> 組件決定它的文本要放大多少。這時(shí)可以使用 $emit 的第二個(gè)參數(shù)來(lái)提供這個(gè)值:

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

然后當(dāng)在父級(jí)組件監(jiān)聽這個(gè)事件的時(shí)候,我們可以通過(guò) $event 訪問(wèn)到被拋出的這個(gè)值:

<blog-post ... @enlarge-text="postFontSize += $event"></blog-post>

或者,如果這個(gè)事件處理函數(shù)是一個(gè)方法:

<blog-post ... @enlarge-text="onEnlargeText"></blog-post>

那么這個(gè)值將會(huì)作為第一個(gè)參數(shù)傳入這個(gè)方法:

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

#在組件上使用 v-model

自定義事件也可以用于創(chuàng)建支持 v-model 的自定義輸入組件。記?。?/p>

<input v-model="searchText" />

等價(jià)于:

<input :value="searchText" @input="searchText = $event.target.value" />

當(dāng)用在組件上時(shí),v-model 則會(huì)這樣:

<custom-input
  :model-value="searchText"
  @update:model-value="searchText = $event"
></custom-input>

WARNING

請(qǐng)注意,我們?cè)谶@里使用的是 model value,因?yàn)槲覀兪褂玫氖?DOM 模板中的 kebab-case。你可以在 DOM Template Parsing Caveats 部分找到關(guān)于 kebab cased 和 camelCased 屬性的詳細(xì)說(shuō)明

為了讓它正常工作,這個(gè)組件內(nèi)的 <input> 必須:

  • 將其 value attribute 綁定到一個(gè)名叫 modelValue 的 prop 上
  • 在其 input 事件被觸發(fā)時(shí),將新的值通過(guò)自定義的 update:modelValue 事件拋出

寫成代碼之后是這樣的:

app.component('custom-input', {
  props: ['modelValue'],
  template: `
    <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
    >
  `
})

現(xiàn)在 v-model 就應(yīng)該可以在這個(gè)組件上完美地工作起來(lái)了:

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

在自定義組件中創(chuàng)建 v-model 功能的另一種方法是使用 computed property 的功能來(lái)定義 getter 和 setter。

在下面的示例中,我們使用計(jì)算屬性重構(gòu) <custom-input> 組件。

請(qǐng)記住,get 方法應(yīng)返回 modelValue property,或用于綁定的任何 property,set 方法應(yīng)為該 property 觸發(fā)相應(yīng)的 $emit。

app.component('custom-input', {
  props: ['modelValue'],
  template: `
    <input v-model="value">
  `,
  computed: {
    value: {
      get() {
        return this.modelValue
      },
      set(value) { this.$emit('update:modelValue', value)
      }
    }
  }
})

現(xiàn)在你只需要了解自定義組件事件,但一旦你讀完本頁(yè)并對(duì)其內(nèi)容還覺得不錯(cuò),我們建議你稍后再閱讀有關(guān)自定義事件

#通過(guò)插槽分發(fā)內(nèi)容

和 HTML 元素一樣,我們經(jīng)常需要向一個(gè)組件傳遞內(nèi)容,像這樣:

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

可能會(huì)渲染出這樣的東西: 點(diǎn)擊此處實(shí)現(xiàn)

幸好,Vue 自定義的 <slot> 元素讓這變得非常簡(jiǎn)單:

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

如你所見,我們只要在需要的地方加入插槽就行了——就這么簡(jiǎn)單!

到目前為止,關(guān)于插槽你需要了解的大概就這些了,如果你閱讀完本頁(yè)內(nèi)容并掌握了它的內(nèi)容,我們會(huì)推薦你再回來(lái)把插槽讀完。

#動(dòng)態(tài)組件

有的時(shí)候,在不同組件之間進(jìn)行動(dòng)態(tài)切換是非常有用的,比如在一個(gè)多標(biāo)簽的界面里: 點(diǎn)擊此處實(shí)現(xiàn)

上述內(nèi)容可以通過(guò) Vue 的 <component> 元素加一個(gè)特殊的 is attribute 來(lái)實(shí)現(xiàn):

<!-- 組件會(huì)在 `currentTabComponent` 改變時(shí)改變 -->
<component :is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括

  • 已注冊(cè)組件的名字,或
  • 一個(gè)組件的選項(xiàng)對(duì)象

你可以在這里查閱并體驗(yàn)完整的代碼,或在這個(gè)版本了解綁定組件選項(xiàng)對(duì)象,而不是已注冊(cè)組件名的示例。

請(qǐng)留意,這個(gè) attribute 可以用于常規(guī) HTML 元素,但這些元素將被視為組件,這意味著所有的 attribute 都會(huì)作為 DOM attribute 被綁定。對(duì)于像 value 這樣的 property,若想讓其如預(yù)期般工作,你需要使用 .prop 修飾器。

到目前為止,關(guān)于動(dòng)態(tài)組件你需要了解的大概就這些了,如果你閱讀完本頁(yè)內(nèi)容并掌握了它的內(nèi)容,我們會(huì)推薦你再回來(lái)把動(dòng)態(tài) & 異步組件讀完。

#解析 DOM 模板時(shí)的注意事項(xiàng)

有些 HTML 元素,諸如 <ul>、<ol>、<table><select>,對(duì)于哪些元素可以出現(xiàn)在其內(nèi)部是有嚴(yán)格限制的。而有些元素,諸如 <li>、<tr><option>,只能出現(xiàn)在其它某些特定的元素內(nèi)部。

這會(huì)導(dǎo)致我們使用這些有約束條件的元素時(shí)遇到一些問(wèn)題。例如:

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

這個(gè)自定義組件 <blog-post-row> 會(huì)被作為無(wú)效的內(nèi)容提升到外部,并導(dǎo)致最終渲染結(jié)果出錯(cuò)。幸好這個(gè)特殊的 v-is attribute 給了我們一個(gè)變通的辦法:

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

WARNING

v-is 值應(yīng)為 JavaScript 字符串文本:

<!-- 錯(cuò)誤的,這樣不會(huì)渲染任何東西 -->
<tr v-is="blog-post-row"></tr>


<!-- 正確的 -->
<tr v-is="'blog-post-row'"></tr>

另外,HTML 屬性名不區(qū)分大小寫,因此瀏覽器將把所有大寫字符解釋為小寫。這意味著當(dāng)你在 DOM 模板中使用時(shí),駝峰 prop 名稱和 event 處理器參數(shù)需要使用它們的 kebab-cased (橫線字符分隔) 等效值:

//  在JavaScript中的駝峰


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

<!-- 在HTML則是橫線字符分割 -->


<blog-post post-title="hello!"></blog-post>

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

  • 字符串模板 (例如:template: '...')
  • 單文件組件
  • <script type="text/x-template">

到這里,你需要了解的解析 DOM 模板時(shí)的注意事項(xiàng)——實(shí)際上也是 Vue 的全部必要內(nèi)容,大概就是這些了。恭喜你!接下來(lái)還有很多東西要去學(xué)習(xí),不過(guò)首先,我們推薦你先休息一下,試用一下 Vue,自己隨意做些好玩的東西。

如果你感覺已經(jīng)掌握了這些知識(shí),我們推薦你再回來(lái)把完整的組件&異步組件指南,包括側(cè)邊欄中組件深入章節(jié)的所有頁(yè)面讀完。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)