Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке пропси у Vue?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Пропси (props)** у Vue передають дані від батьківського компонента до дочірнього. Дочірній оголошує, що очікує; батько передає значення через `:propName="value"`. ```vue <UserCard :name="userName" :age="25" /> <script setup> defineProps({ name: String, age: Number }); </script> ``` **Ключове:** пропси тільки для читання всередині дочірнього компонента. Щоб надіслати дані вгору, використовуй emit.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Пропси (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 - ні. Батько передає результат перевірки ролі; дитина лише рендерить. Жоден із компонентів не знає, як працює інший.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.