Skip to content

Основи компонентів

Компоненти дозволяють нам розділити інтерфейс користувача на незалежні частини, які можна багаторазово використовувати, і думати про кожну частину окремо. Зазвичай програму організовують у дерево вкладених компонентів:

Component Tree

Це дуже схоже на те, як ми вкладаємо рідні HTML-елементи, але Vue реалізує власну модель компонентів, яка дозволяє нам інкапсулювати власний вміст і логіку в кожному компоненті. Vue також чудово працює з рідними вебкомпонентами. Якщо вас цікавить зв'язок між компонентами Vue і рідними вебкомпонентами, читайте деталі тут.

Оголошення компонента

Коли використовується етап збірки, як правило, ми визначаємо кожен компонент Vue у спеціальному файлі за допомогою розширення .vue, відомого як однофайловий компонент (скорочено SFC, скорочено від (англ.) Single File Component):

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

<template>
  <button @click="count++">Ви натиснули на мене {{ count }} разів.</button>
</template>
vue
<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Ви натиснули на мене {{ count }} разів.</button>
</template>

Якщо не використовується етап збірки, компонент Vue можна визначити як звичайний об'єкт JavaScript, що містить параметри Vue:

js
export default {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      Ви натиснули на мене {{ count }} разів.
    </button>`
}
js
import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return { count }
  },
  template: `
    <button @click="count++">
      Ви натиснули на мене {{ count }} разів.
    </button>`
  // Також можна використати шаблон у DOM:
  // template: '#my-template-element'
}

Тут шаблон вбудовано як рядок JavaScript, який Vue збиратиметься на льоту. Ви також можете використовувати ID селектор, який вказує на елемент (зазвичай рідні елементи <template>) — Vue використовуватиме його вміст як джерело шаблону.

Наведений вище приклад визначає один компонент і експортує його як стандартний експорт файлу .js, але ви можете використовувати іменовані експорти для експорту кількох компонентів з одного файлу.

Використання компонента

TIP

Ми використовуватимемо синтаксис SFC для решти цього посібника - концепції навколо компонентів однакові, незалежно від того, використовуєте ви крок збірки чи ні. У розділі Приклади показано використання компонентів в обох сценаріях.

Щоб використовувати дочірній компонент, нам потрібно імпортувати його в батьківський компонент. Якщо припустити, що ми розмістили наш компонент лічильника у файлі під назвою ButtonCounter.vue, компонент буде виділено в стандартний експорт файлу:

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

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

<template>
  <h1>Ось дочірній компонент!</h1>
  <ButtonCounter />
</template>

Щоб показати імпортований компонент у нашому шаблоні, нам потрібно зареєструвати його за допомогою параметра components. Після цього компонент буде доступний як тег із використанням ключа, під яким він зареєстрований.

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

<template>
  <h1>Ось дочірній компонент!</h1>
  <ButtonCounter />
</template>

За допомогою <script setup> імпортовані компоненти автоматично стають доступними для шаблону.

Також можна глобально зареєструвати компонент, зробивши його доступним для всіх компонентів даного додатку без необхідності його імпорту. Плюси та мінуси глобальної та локальної реєстрації обговорюються в спеціальному розділі Реєстрація компонентів.

Компоненти можна повторно використовувати скільки завгодно разів:

template
<h1>Тут багато дочірніх компонентів!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

Зауважте, що під час натискання кнопок кожна з них підтримує власний окремий count. Це тому, що кожного разу, коли ви використовуєте компонент, створюється новий його екземпляр.

У SFC рекомендується використовувати PascalCase назви тегів для дочірніх компонентів, щоб відрізнити їх від рідних елементів HTML. Хоча рідні імена тегів HTML не чутливі до регістру, Vue SFC є скомпільованим форматом, тому ми можемо використовувати в ньому імена тегів з урахуванням регістру. Ми також можемо використовувати />, щоб закрити тег.

Якщо ви створюєте свої шаблони безпосередньо в DOM (наприклад, як вміст рідного елемента <template>), шаблон підлягатиме стандартному синтаксичному аналізу HTML браузером. У таких випадках вам потрібно буде використовувати kebab-case і явні закривальні теги для компонентів:

template
<!-- якщо цей шаблон написаний у DOM -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>

Дивіться застереження щодо аналізу шаблону DOM для отримання додаткової інформації.

Передавання реквізитів

Якщо ми створюємо блог, нам, швидше за все, знадобиться компонент, який представлятиме публікацію блогу. Ми хочемо, щоб усі публікації в блозі мали однаковий візуальний макет, але мали різний вміст. Такий компонент не буде корисним, якщо ви не зможете передати йому дані, наприклад назву та вміст конкретної публікації, яку ми хочемо показати. Ось тут і з’являється реквізит.

Реквізити — це власні атрибути, які можна зареєструвати в компоненті. Щоб передати заголовок до нашого компонента допису в блозі, ми повинні оголосити його в списку реквізитів, які приймає цей компонент, використовуючи параметр propsмакрос defineProps:

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

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

Коли значення передається до реквізитного атрибута, воно стає властивістю цього екземпляра компонента. Значення цієї властивості доступне в шаблоні та в контексті this компонента, як і будь-яка інша властивість компонента.

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

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

defineProps — це макрос часу компіляції, який доступний лише всередині <script setup> і не потребує явного імпорту. Оголошені властивості автоматично показуються в шаблоні. defineProps також повертає об’єкт, який містить усі властивості, передані компоненту, щоб ми могли отримати до них доступ у JavaScript, якщо потрібно:

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

Також до вашої уваги: Типізація реквізитів компонента

Якщо ви не використовуєте <script setup>, реквізити слід оголошувати за допомогою параметра props, а об’єкт props буде передано до setup() як перший аргумент:

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

Компонент може мати скільки завгодно реквізитів, і за замовчуванням будь-яке значення можна передати будь-якому реквізиту.

Після того, як реквізит зареєстровано, ви можете передати йому дані як спеціальний атрибут, наприклад:

template
<BlogPost title="Моя подорож з Vue" />
<BlogPost title="Ведення блогу з Vue" />
<BlogPost title="Чому Vue такий чудовий" />

Однак у типовій програмі ви, ймовірно, матимете масив публікацій у своєму батьківському компоненті:

js
export default {
  // ...
  data() {
    return {
      posts: [
        { id: 1, title: 'Моя подорож з Vue' },
        { id: 2, title: 'Ведення блогу з Vue' },
        { id: 3, title: 'Чому Vue такий чудовий' }
      ]
    }
  }
}
js
const posts = ref([
  { id: 1, title: 'Моя подорож з Vue' },
  { id: 2, title: 'Ведення блогу з Vue' },
  { id: 3, title: 'Чому Vue такий чудовий' }
])

Потім показати компонент для кожного за допомогою v-for:

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

Зверніть увагу, як v-bind використовується для передачі динамічних значень реквізитів. Це особливо корисно, коли ви заздалегідь не знаєте точного вмісту, який збираєтеся показати.

Наразі це все, що вам потрібно знати про реквізити, але коли ви закінчите читати цю сторінку та відчуєте себе комфортно з її вмістом, радимо повернутися пізніше, щоб прочитати повний посібник по реквізитах.

Прослуховування подій

Коли ми розробляємо наш компонент <BlogPost>, для деяких функцій може знадобитися зворотна комунікація з батьківським компонентом. Наприклад, ми можемо вирішити включити функцію доступності, щоб збільшити текст дописів блогу, залишивши решту сторінки розміру за замовчуванням.

У батьківському компоненті ми можемо впровадити цю функцію, додавши властивість данихref postFontSize:

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

const postFontSize = ref(1)

Яку можна використовувати в шаблоні для керування розміром шрифту всіх публікацій блогу:

template
<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>Збільшити текст</button>
  </div>
</template>

Кнопка ще нічого не робить — ми хочемо, щоб натискання кнопки повідомляло батьківському компоненту, що він має збільшити текст усіх дописів. Щоб розв'язати цю проблему, компоненти забезпечують спеціальну систему подій. Батьківський компонент може обрати прослуховування будь-якої події в екземплярі дочірнього компонента за допомогою v-on або @, так само як ми робимо це з нативною подією DOM:

template
<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')">Збільшити текст</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, часто корисно мати можливість передавати вміст до компонента, наприклад:

template
<AlertBox>
  Сталося щось погане.
</AlertBox>

Що може показати щось на зразок:

Це помилка для демонстраційних цілей

Сталося щось погане.

Цього можна досягти за допомогою спеціального Vue-елемента <slot>:

vue
<template>
  <div class="alert-box">
    <strong>Це помилка для демонстраційних цілей</strong>
    <slot />
  </div>
</template>

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

Як ви бачите вище, ми використовуємо <slot> як підмінний елемент, замість якого ми хочемо розмістити вміст – і все. Ми закінчили!

Наразі це все, що вам потрібно знати про слоти, але коли ви закінчите читати цю сторінку та відчуєте себе комфортно з її вмістом, рекомендуємо повернутися пізніше, щоб прочитати повний посібник про слоти.

Динамічні компоненти

Іноді корисно динамічно перемикатися між компонентами, наприклад, в інтерфейсі з вкладками:

Це стало можливим завдяки елементу <component> Vue зі спеціальним атрибутом is:

template
<!-- Компонент змінюється, коли змінюється поточна вкладка -->
<component :is="currentTab"></component>
template
<!-- Компонент змінюється, коли змінюється поточна вкладка -->
<component :is="tabs[currentTab]"></component>

У наведеному вище прикладі значення, передане в :is, може містити:

  • рядок імені зареєстрованого компонента, АБО
  • фактичний імпортований об'єкт компонента

Ви також можете використовувати атрибут is для створення звичайних елементів HTML.

Під час перемикання між декількома компонентами за допомогою <component :is="..."> компонент буде демонтовано, коли з нього буде перемкнуто. Ми можемо змусити неактивні компоненти залишатися «живими» за допомогою вбудованого компонента <KeepAlive>.

Застереження щодо аналізу шаблону DOM

Якщо ви пишете свої шаблони Vue безпосередньо в DOM, Vue доведеться отримати рядок шаблону з DOM. Це призводить до деяких застережень через власну поведінку браузерів під час аналізу HTML.

TIP

Слід зазначити, що обмеження, описані нижче, застосовуються, лише якщо ви пишете свої шаблони безпосередньо в DOM. Вони НЕ застосовуються, якщо ви використовуєте шаблони рядків із таких джерел:

  • Однофайлові компоненти
  • Вбудовані рядки шаблону (наприклад, template: '...')
  • <script type="text/x-template">

Нечутливість до регістру

Теги HTML і назви атрибутів нечутливі до регістру, тому браузери сприйматимуть будь-які символи верхнього регістру як малі. Це означає, що коли ви використовуєте шаблони DOM, назви компонентів в PascalCase та назви реквізитів у в регістрі camelCase або назви подій v-on повинні використовувати свої еквіваленти у регістрі kebab (розділені дефісами):

js
// camelCase в JavaScript
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
template
<!-- kebab-case в HTML -->
<blog-post post-title="привіт!" @update-post="onUpdatePost"></blog-post>

Закривальні теги

Ми використовували закривальні теги для компонентів у попередніх зразках коду:

template
<MyComponent />

Це пояснюється тим, що синтаксичний аналізатор шаблону Vue розглядає /> як вказівку на закінчення будь-якого тегу, незалежно від його типу.

Однак у шаблонах DOM ми завжди повинні включати явні закривальні теги:

template
<my-component></my-component>

Це тому, що специфікація HTML дозволяє лише декільком конкретним елементам опускати закривальні теги, найпоширенішими є <input> і <img>. Для всіх інших елементів, якщо ви опустите закривальний тег, рідний синтаксичний аналізатор HTML вважатиме, що ви не завершили відкривальний тег. Наприклад, такий фрагмент коду:

template
<my-component /> <!-- ми маємо намір закрити тег тут... -->
<span>привіт</span>

will be parsed as:

template
<my-component>
  <span>привіт</span>
</my-component> <!-- але браузер закриє його тут. -->

Обмеження розміщення елементів

Окремі елементи HTML, як-от <ul>, <ol>, <table> і <select>, мають обмеження щодо того, які елементи можуть з’являтися в них, а певні елементи, наприклад <li>, <tr> і <option> можуть з'являтися лише всередині деяких інших елементів.

Це призведе до проблем під час використання компонентів з елементами, які мають такі обмеження. Наприклад:

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

Спеціальний компонент <blog-post-row> буде видалено як недійсний вміст, що призведе до помилок в остаточному відтвореному виведенні. Ми можемо використати спеціальний атрибут is як обхідний шлях:

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

TIP

При використанні в нативних елементах HTML, значення is має мати префікс vue:, щоб інтерпретуватися як компонент Vue. Це потрібно, щоб уникнути плутанини з рідними власними вбудованими елементами.

Це все, що вам наразі потрібно знати про застереження щодо аналізу шаблонів DOM – і, власне, кінець Основам від Vue. Щиро вітаю! Ще є чому навчитися, але спочатку ми рекомендуємо зробити перерву, щоб пограти з Vue самостійно - створіть щось веселе або перегляньте приклади, якщо ви ще цього не зробили.

Коли ви відчуєте себе комфортно зі знаннями, які ви щойно засвоїли, перейдіть до посібника, щоб дізнатися більше про компоненти.

Основи компонентів has loaded