Skip to main content

Що таке пропси у Vue?

Пропси (props) - це механізм Vue для передачі даних від батьківського компонента до дочірнього. Дочірній компонент отримує типізований інтерфейс ззовні і не може змінювати те, що йому передали.

Теорія

TL;DR

  • Дані йдуть в одному напрямку: від батька до дитини. Дитина не змінює пропси напряму.
  • Думай про компонент як про функцію: пропси - це її аргументи.
  • Оголоси пропси в дочірньому компоненті; передай значення з батьківського через :propName="value".
  • Дитина має оновити дані? Зроби emit події вгору. Не мутуй проп.
  • Більше 2-3 рівнів вкладеності? Переходь на provide/inject або Pinia.

Оголошення та використання пропсів

vue
<!-- Батько: App.vue --> <template> <UserCard :name="userName" :age="userAge" /> </template> <script> export default { data() { return { userName: 'Alice', userAge: 25 }; } }; </script> <!-- Дитина: UserCard.vue --> <template> <div>{{ name }} - {{ age }} років</div> </template> <script> export default { props: { name: String, age: Number } }; </script>

Батько тримає дані. Дитина отримує їх через пропси і рендерить.

Односторонній потік даних

Пропси створюють односторонній канал від батька до дитини. Коли userName змінюється у батьківському компоненті, Vue автоматично оновлює name у дочірньому. Але дитина не може надіслати зміни назад через проп. Це задум, а не обмеження. Батько керує станом, дитина його рендерить.

Це спрощує розуміння коду. Дивишся на дочірній компонент і знаєш: всі вхідні дані приходять через пропси, нічого зайвого немає.

Коли використовувати

  • Статична конфігурація (текст кнопки, іконка) → передай як проп.
  • Динамічні дані (профіль користувача, список елементів) → теж проп.
  • Дитина має змінити значення → зроби emit події вгору до батька.
  • Дані потрібні одночасно в багатьох компонентах → використовуй Pinia або provide/inject.

Валідація пропсів

Vue дозволяє оголошувати не лише тип. Можна зробити проп обов'язковим, задати значення за замовчуванням або додати власну функцію-валідатор:

vue
<script setup> const props = defineProps({ name: { type: String, required: true }, age: { type: Number, default: 18 }, items: { type: Array, default: () => [], // фабрична функція, не літерал validator: (val) => val.every(item => typeof item === 'string') } }); </script>

В режимі розробки Vue виводить попередження, якщо валідація не пройшла. У продакшені нічого не зламається, але попередження - сигнал що батько передає щось не те.

Як Vue обробляє пропси всередині

Коли Vue компілює <UserCard :name="value" />, він перетворює це на createVNode(UserCard, { name: value }). Під час виконання Vue загортає дочірній інстанс у Proxy і відстежує реактивний стан батька через effect. При зміні пропу Vue ставить оновлення DOM у чергу через queueJob, збираючи кілька змін в одне оновлення. Дочірній компонент просто читає props.name - вся механіка прихована.

Типові помилки

Мутація пропу напряму. Vue попередить у режимі розробки, і зміна не збережеться.

js
// Неправильно props.count++; // Правильно: emit зміну вгору emit('update:count', props.count + 1);

Об'єкт або масив як значення за замовчуванням без фабричної функції. Всі інстанси компонента поділяють одне посилання. Це найпоширеніша помилка, яку я бачу при перегляді Vue-коду в продакшені.

js
// Неправильно: всі інстанси поділяють цей масив default: [] // Правильно: новий масив для кожного інстансу default: () => []

Передача значення без :. <Comp name="val"> передає рядок "val". <Comp :name="val"> передає змінну. Легко пропустити під час рев'ю шаблону.

Забути kebab-case в шаблоні. В JS оголошуєш isAdmin, але в шаблоні пишеш :is-admin="true". Vue їх зв'язує, але тільки якщо дотримуватись конвенції.

Де зустрічається в реальних проектах

  • Компоненти Vuetify приймають пропси color, elevation і variant для налаштування кнопок і карток по всьому застосунку.
  • Element Plus використовує проп model-value разом з emit update:modelValue для підтримки v-model у полях форм.
  • Nuxt-проекти передають масив posts як проп до компонентів-списків для контентних сторінок.
  • Кодова база GitLab на Vue активно використовує пропси для рендеру динамічних списків користувачів у вигляді запитів на злиття.

Питання на співбесіді

Q: Яка різниця між пропсами і станом?
A: Стан (data() або ref()) - внутрішній і мутабельний. Пропси - зовнішні вхідні дані від батька, тільки для читання всередині дочірнього компонента.

Q: Як зробити валідацію пропу у Vue?
A: Використовуй type, required, default і validator в оголошенні пропу. Vue виводить попередження при невідповідності в режимі розробки.

Q: Що змінилось у пропсах у Vue 3?
A: Vue 3 додає defineProps() для <script setup>, що дає автоматичне виведення типів з TypeScript. Поведінка під час виконання така сама як у Vue 2.

Q: Як реалізувати двосторонню прив'язку через пропси?
A: Оголоси проп modelValue і зроби emit update:modelValue. Батько використовує v-model, що є скороченням для обох одразу.

Q: Як реактивність пропсів працює з глибокими об'єктами у Vue 3?
A: Vue загортає пропси в поверхневий (shallow) Proxy. Зміна примітивного пропу автоматично запускає перерендер. Глибокі мутації всередині об'єкта не відстежуються без watch з { deep: true } або заміни посилання у батьку. Мутування вкладених об'єктів у пропсах - це саме той тип багу, який важко потім відстежити.

Приклади

Базовий: відображення даних користувача в картці

vue
<!-- Батько --> <template> <UserCard :name="user.name" :age="user.age" /> </template> <script setup> import { ref } from 'vue'; import UserCard from './UserCard.vue'; const user = ref({ name: 'Alice', age: 25 }); </script> <!-- Дитина: UserCard.vue --> <script setup> defineProps({ name: { type: String, required: true }, age: { type: Number, default: 18 } }); </script> <template> <div class="card"> <h2>{{ name }}</h2> <p>Вік: {{ age }}</p> </div> </template>

Дочірній компонент нічого не знає про структуру даних батька. Він рендерить те, що отримав.

Середній рівень: список користувачів зі значком адміна

vue
<!-- Батько: UsersList.vue --> <template> <UserCard v-for="user in users" :key="user.id" :name="user.name" :avatar="user.avatar" :is-admin="user.role === 'admin'" /> </template> <script setup> import { ref } from 'vue'; import UserCard from './UserCard.vue'; const users = ref([ { id: 1, name: 'Bob', avatar: '/bob.jpg', role: 'admin' }, { id: 2, name: 'Carol', avatar: '/carol.jpg', role: 'user' } ]); </script> <!-- Дитина: UserCard.vue --> <script setup> defineProps({ name: String, avatar: String, isAdmin: Boolean }); </script> <template> <div class="user-card"> <img :src="avatar" :alt="name" /> <span>{{ name }}</span> <span v-if="isAdmin" class="badge">Admin</span> </div> </template>

Bob отримує значок адміна. Carol - ні. Батько передає результат перевірки ролі; дитина лише рендерить. Жоден із компонентів не знає, як працює інший.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?