Що таке пропси у Vue?
Пропси (props) - це механізм Vue для передачі даних від батьківського компонента до дочірнього. Дочірній компонент отримує типізований інтерфейс ззовні і не може змінювати те, що йому передали.
Теорія
TL;DR
- Дані йдуть в одному напрямку: від батька до дитини. Дитина не змінює пропси напряму.
- Думай про компонент як про функцію: пропси - це її аргументи.
- Оголоси пропси в дочірньому компоненті; передай значення з батьківського через
:propName="value". - Дитина має оновити дані? Зроби emit події вгору. Не мутуй проп.
- Більше 2-3 рівнів вкладеності? Переходь на
provide/injectабо Pinia.
Оголошення та використання пропсів
<!-- Батько: 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 дозволяє оголошувати не лише тип. Можна зробити проп обов'язковим, задати значення за замовчуванням або додати власну функцію-валідатор:
<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 попередить у режимі розробки, і зміна не збережеться.
// Неправильно
props.count++;
// Правильно: emit зміну вгору
emit('update:count', props.count + 1);Об'єкт або масив як значення за замовчуванням без фабричної функції. Всі інстанси компонента поділяють одне посилання. Це найпоширеніша помилка, яку я бачу при перегляді Vue-коду в продакшені.
// Неправильно: всі інстанси поділяють цей масив
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разом з emitupdate: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 } або заміни посилання у батьку. Мутування вкладених об'єктів у пропсах - це саме той тип багу, який важко потім відстежити.
Приклади
Базовий: відображення даних користувача в картці
<!-- Батько -->
<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>Дочірній компонент нічого не знає про структуру даних батька. Він рендерить те, що отримав.
Середній рівень: список користувачів зі значком адміна
<!-- Батько: 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 - ні. Батько передає результат перевірки ролі; дитина лише рендерить. Жоден із компонентів не знає, як працює інший.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.