跳轉到內容

元件基礎

元件允許我們將UI拆分為獨立和可重用的部分,並單獨考慮每個部分。一個應用通常組織成巢狀元件的樹

Component Tree

這與我們巢狀原生的HTML元素非常相似,但Vue實現了自己的元件模型,允許我們在每個元件中封裝自定義內容和邏輯。Vue也與原生的Web元件很好地配合。如果您對Vue元件和原生Web元件之間的關係感興趣,請在此瞭解更多

定義元件

當使用構建步驟時,我們通常使用.vue副檔名定義每個Vue元件在專用檔案中,稱為單檔案元件(SFC)

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">You clicked me {{ count }} times.</button>
</template>

當不使用構建步驟時,Vue元件可以定義為一個包含Vue特定選項的普通JavaScript物件

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
  // Can also target an in-DOM template:
  // template: '#my-template-element'
}

這裡將模板內聯為JavaScript字串,Vue會即時編譯它。您還可以使用ID選擇器指向一個元素(通常是原生的<template>元素)- Vue將使用其內容作為模板源。

上述示例定義了一個單個元件,並將其作為 .js 檔案的預設匯出匯出,但您可以使用命名匯出從同一檔案中匯出多個元件。

使用元件

提示

本指南的其餘部分將使用 SFC 語法 - 無論是否使用構建步驟,元件的概念都是相同的。《示例》部分顯示了兩種情況下的元件使用。

要使用子元件,我們需要在父元件中匯入它。假設我們將計數器元件放在名為 ButtonCounter.vue 的檔案中,該元件將作為檔案的預設匯出

vue
<script>
import ButtonCounter from './ButtonCounter.vue'

export default {
  components: {
    ButtonCounter
  }
}
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

為了將匯入的元件暴露給我們的模板,我們需要使用 components 選項將其註冊。然後,元件將可以使用其註冊的鍵作為標籤使用。

vue
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>

<template>
  <h1>Here is a child component!</h1>
  <ButtonCounter />
</template>

使用 <script setup>,匯入的元件將自動對模板可用。

還可以全域性註冊元件,使其在給定應用程式的所有元件中可用,而無需匯入。全域性註冊與本地註冊的優缺點在專門的《元件註冊》部分中討論。

元件可以按需重複使用

模板
<h1>Here are many child components!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

請注意,當點選按鈕時,每個按鈕都保持自己的獨立 count。這是因為每次使用元件時,都會建立一個新的 例項

在 SFC 中,建議使用 PascalCase 標籤名來命名子元件,以便與原生 HTML 元素區分開來。雖然原生 HTML 標籤名不區分大小寫,但 Vue SFC 是一個編譯格式,因此我們能夠在其中使用大小寫敏感的標籤名。我們還可以使用 /> 來關閉標籤。

如果您直接在 DOM 中編寫模板(例如,作為原生 <template> 元素的內容),則模板將受瀏覽器原生 HTML 解析行為的影響。在這種情況下,您需要使用 kebab-case 和顯式關閉標籤來為元件

模板
<!-- if this template is written in the DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

有關更多詳細資訊,請參閱 in-DOM 模板解析注意事項

傳遞 Props

如果我們正在構建一個部落格,我們可能需要一個表示部落格文章的元件。我們希望所有部落格文章都具有相同的視覺佈局,但內容不同。這樣的元件除非您可以向它傳遞資料(例如,我們想要顯示的特定文章的標題和內容),否則將沒有用處。這正是 props 的作用。

Props 是您可以註冊在元件上的自定義屬性。要向我們的部落格文章元件傳遞標題,我們必須在元件接受的 props 列表中宣告它,使用 props 選項defineProps

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title']
}
</script>

<template>
  <h4>{{ title }}</h4>
</template>

當值傳遞給 prop 屬性時,它成為該元件例項的一個屬性。該屬性的值在模板和元件的 this 上下文中都是可訪問的,就像任何其他元件屬性一樣。

vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>

<template>
  <h4>{{ title }}</h4>
</template>

defineProps 是一個編譯時宏,僅在 <script setup> 內部可用,無需顯式匯入。宣告的 props 將自動暴露給模板。defineProps 還返回一個包含傳遞給元件的所有 props 的物件,以便我們可以在需要時在 JavaScript 中訪問它們

js
const props = defineProps(['title'])
console.log(props.title)

另請參閱:為元件 Props 型別化

如果您不使用 <script setup>,則應使用 props 選項來宣告屬性,並且屬性物件將被傳遞給 setup() 作為第一個引數

js
export default {
  props: ['title'],
  setup(props) {
    console.log(props.title)
  }
}

元件可以有任意多的屬性,並且預設情況下,任何值都可以傳遞給任何屬性。

一旦註冊了屬性,您就可以像這樣將其作為自定義屬性傳遞資料

模板
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />

然而,在典型的應用程式中,您可能在父元件中有一個帖子陣列

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

然後想要使用 v-for 為每個帖子渲染一個元件

模板
<BlogPost
  v-for="post in posts"
  :key="post.id"
  :title="post.title"
 />

請注意,如何使用 v-bind 語法:title="post.title")來傳遞動態屬性值。這在您不知道將要渲染的確切內容時非常有用。

這就是您現在需要了解的所有關於屬性的內容,但是一旦您完成閱讀此頁面並熟悉其內容,我們建議稍後返回閱讀關於 屬性 的完整指南。

監聽事件

隨著我們開發 <BlogPost> 元件,一些功能可能需要向上與父元件通訊。例如,我們可能決定包含一個輔助功能來放大部落格文章的文字,同時保持頁面其餘部分預設大小。

在父元件中,我們可以透過新增一個 postFontSize 資料屬性ref

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

const postFontSize = ref(1)

這可以在模板中使用來控制所有部落格文章的字型大小

模板
<div :style="{ fontSize: postFontSize + 'em' }">
  <BlogPost
    v-for="post in posts"
    :key="post.id"
    :title="post.title"
   />
</div>

現在讓我們在 <BlogPost> 元件的模板中新增一個按鈕

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button>Enlarge text</button>
  </div>
</template>

按鈕目前沒有任何功能 - 我們希望點選按鈕能讓父元件知道應該放大所有帖子的文字。為了解決這個問題,元件提供了一個自定義事件系統。父元件可以選擇使用 v-on@ 來監聽子元件例項上的任何事件,就像我們使用原生DOM事件一樣

模板
<BlogPost
  ...
  @enlarge-text="postFontSize += 0.1"
 />

然後子元件可以透過呼叫內建的 $emit 方法 在自己上發出事件,傳遞事件的名稱

vue
<!-- BlogPost.vue, omitting <script> -->
<template>
  <div class="blog-post">
    <h4>{{ title }}</h4>
    <button @click="$emit('enlarge-text')">Enlarge text</button>
  </div>
</template>

多虧了 @enlarge-text="postFontSize += 0.1" 監聽器,父元件將接收到事件並更新 postFontSize 的值。

我們可以選擇性地使用 emits 選項defineEmits 來聲明發出的事件

vue
<!-- BlogPost.vue -->
<script>
export default {
  props: ['title'],
  emits: ['enlarge-text']
}
</script>
vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>

這記錄了元件發出的所有事件,並可選擇 驗證它們。它還允許Vue避免將它們隱式地應用於子元件根元素的原生監聽器。

defineProps 類似,defineEmits 只能在 <script setup> 中使用,且不需要匯入。它返回一個等同於 $emit 方法的 emit 函式。它可以在元件的 <script setup> 部分中使用,在那裡 $emit 不能直接訪問

vue
<script setup>
const emit = defineEmits(['enlarge-text'])

emit('enlarge-text')
</script>

另請參閱: 元件發出事件的型別化

如果您不使用 <script setup>,您可以使用 emits 選項來聲明發出的事件。您可以將 emit 函式作為 setup 上下文的一個屬性訪問,它作為第二個引數傳遞給 setup()

js
export default {
  emits: ['enlarge-text'],
  setup(props, ctx) {
    ctx.emit('enlarge-text')
  }
}

現在您需要了解關於自定義元件事件的全部內容,但是當您閱讀完本頁並對其內容感到熟悉後,我們建議您稍後回來閱讀關於自定義事件的完整指南。

使用插槽進行內容分發

就像HTML元素一樣,經常需要將內容傳遞給元件,如下所示

模板
<AlertBox>
  Something bad happened.
</AlertBox>

可能渲染出類似的內容

這是一個用於演示的錯誤

發生了某些事情。

這可以透過使用Vue的自定義<slot>元素實現

vue
<!-- AlertBox.vue -->
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot />
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>

如您所看到的,我們使用<slot>作為內容要放置的佔位符 - 這就完成了。我們已經完成了!

現在您需要了解關於插槽的全部內容,但是當您閱讀完本頁並對其內容感到熟悉後,我們建議您稍後回來閱讀關於插槽的完整指南。

動態元件

有時,在標籤頁介面中動態切換元件是有用的

上面的內容是透過Vue的<component>元素和特殊的is屬性實現的

模板
<!-- Component changes when currentTab changes -->
<component :is="currentTab"></component>
模板
<!-- Component changes when currentTab changes -->
<component :is="tabs[currentTab]"></component>

在上面的示例中,傳遞給:is的值可以是

  • 已註冊元件的名稱字串,或者
  • 實際匯入的元件物件

您還可以使用is屬性來建立常規HTML元素。

當使用<component :is="...">在多個元件之間切換時,當切換到其他元件時,元件將被解除安裝。我們可以使用內建的<KeepAlive>元件強制非活動元件保持“活躍”。

in-DOM 模板解析注意事項

如果您直接在DOM中編寫Vue模板,Vue將不得不從DOM中檢索模板字串。由於瀏覽器原生HTML解析行為,這導致了一些注意事項。

提示

以下討論的限制僅適用於您直接在DOM中編寫模板的情況。它們不適用於您從以下來源使用字串模板的情況

  • 單檔案元件
  • 內聯模板字串(例如 template: '...'
  • <script type="text/x-template">

不區分大小寫

HTML標籤和屬性名稱不區分大小寫,因此瀏覽器將任何大寫字元解釋為小寫。這意味著當您使用in-DOM模板時,PascalCase元件名稱和camelCased屬性名或v-on事件名都需要使用它們的kebab-cased(短橫線分隔)等效名稱

js
// camelCase in JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
模板
<!-- kebab-case in HTML -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

自閉合標籤

我們在之前的程式碼示例中使用了元件的自閉合標籤

模板
<MyComponent />

這是因為Vue的模板解析器將/>視為結束任何標籤的指示,無論其型別如何。

然而,在in-DOM模板中,我們必須始終包含顯式的關閉標籤

模板
<my-component></my-component>

這是因為在HTML規範中,只有少數幾個特定元素可以省略閉合標籤,最常見的有<input><img>。對於所有其他元素,如果你省略了閉合標籤,原生的HTML解析器會認為你從未結束開標籤。例如,以下程式碼片段

模板
<my-component /> <!-- we intend to close the tag here... -->
<span>hello</span>

會被解析為

模板
<my-component>
  <span>hello</span>
</my-component> <!-- but the browser will close it here. -->

元素放置限制

一些HTML元素,如<ul><ol><table><select>,對其內部可以出現哪些元素有限制,而一些元素如<li><tr><option>只能出現在某些其他元素內部。

這在使用具有此類限制的元素元件時會導致問題。例如

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

自定義元件<blog-post-row>將被提升為無效內容,導致最終渲染輸出中的錯誤。我們可以使用特殊的is屬性作為解決方案

模板
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

提示

當用於原生HTML元素時,is的值必須以vue:為字首,才能被視為Vue元件。這是為了避免與原生的自定義內建元素混淆。

這就是目前你需要了解的DOM模板解析的注意事項——實際上,也是Vue的《基礎知識》的結束。恭喜!還有很多東西要學,但首先,我們建議你先休息一下,親自嘗試一下Vue——構建一些有趣的東西,或者檢視一些示例,如果你還沒有的話。

一旦你對你剛剛吸收的知識感到自信,繼續閱讀指南,深入學習元件。

元件基礎知識已載入