Vue.js: Загальний
20 питань · ~15 хв · Версія v3.0
Увійдіть, щоб продовжити
Компоненти, реактивність, Composition API, директиви.
- За спробу
- 20
- У пулі
- 100
- Проходжень
- 20
- Середній бал
- 89%
- Пройшли на 70%+
- 95%
Питання для підготовки
108 питаньОбидва створюють реактивний стан, за яким Vue стежить і перемальовує шаблон при змінах.
ref(value)- обгортка над будь-яким значенням: числом, рядком, об'єктом. Значення лежить у.value. У шаблоні.valueписати не треба - Vue розгортає ref автоматично.reactive(object)- реактивний проксі над об'єктом (чи масивом,Map). Поля читаються напряму, без.value.
import { ref, reactive } from 'vue';
const count = ref(0);
count.value++;
const form = reactive({ email: '', password: '' });
form.email = 'olia@example.com';
Обмеження reactive:
- Працює лише з об'єктами -
reactive(0)не має сенсу. - Не можна замінити цілком:
form = reactive({...})розриває зв'язок із шаблоном, бо шаблон стежить за старим проксі. - Деструктуризація губить реактивність:
const { email } = formдає звичайний рядок.
Через ці обмеження документація Vue радить ref як основний інструмент: його можна передавати у функції, повертати з composables, перезаписувати цілком (items.value = newItems), і поведінка завжди однакова. reactive зручний для згрупованого стану на кшталт форми, який не замінюють і не деструктуризують.
computed - значення, похідне від іншого стану. Vue кешує результат і перераховує його, лише коли змінилися реактивні залежності.
const items = ref([]);
const total = computed(() => items.value.reduce((sum, i) => sum + i.price, 0));
Метод у шаблоні ({{ calcTotal() }}) викликається при кожному рендері компонента, навіть якщо items не змінювалися. Для важких обчислень це зайва робота.
watch - не значення, а побічна дія у відповідь на зміну: запит на сервер, запис у localStorage, ручна робота з DOM.
watch(searchQuery, async (query) => {
results.value = await search(query);
});
Як обирати:
- Потрібно значення для шаблону чи коду -
computed. - Потрібно щось зробити, коли значення змінилося, -
watch. - Потрібна дія з аргументами за запитом (обробник кліку) - метод.
Типова помилка - watch, що просто записує похідне значення в інший ref:
watch(items, () => { total.value = sum(items.value); }, { deep: true }); // зайве
Це той самий computed, але з ручною синхронізацією, яку легко зламати.
computed має бути чистим: без запитів, змін іншого стану чи DOM. Інакше результат залежатиме від того, коли й скільки разів Vue вирішить його перерахувати.
watch(source, callback)стежить за явно вказаним джерелом. Колбек отримує нове й старе значення і за замовчуванням не запускається одразу - лише при першій зміні.watchEffect(callback)запускається одразу і сам відстежує всі реактивні значення, які прочитав під час виконання. Старого значення немає.
watch(userId, async (id, oldId) => {
user.value = await fetchUser(id);
});
watchEffect(async () => {
user.value = await fetchUser(userId.value); // залежність від userId знайдена сама
});
Опції watch:
immediate: true- викликати колбек одразу з поточним значенням, якwatchEffect.deep: true- реагувати на зміни вкладених полів об'єкта, а не лише на заміну всього значення. Заreactive-об'єктомwatchстежить глибоко автоматично. З Vue 3.5deepприймає й число - глибину обходу.once: true(3.4+) - спрацювати лише раз.
Нюанси:
watchEffectвідстежує лише те, що прочитано синхронно до першогоawait. Значення, прочитані після, у залежності не потрапляють.deepна великій структурі - дорогий обхід при кожній зміні. Краще стежити за конкретним полем:watch(() => form.email, ...).- Гонки: якщо
userIdзмінився двічі швидко, відповіді можуть прийти не в тому порядку.onCleanup(чиonWatcherCleanupу 3.5) дозволяє скасувати попередній запит.
Правило: watch - коли важливо, що саме змінилося і нові/старі значення; watchEffect - коли ефект просто має відповідати поточному стану.
Реактивність Vue працює через проксі: Vue знає про зміну, бо зміна йде через проксі (state.count = 1). Деструктуризація копіює значення полів у звичайні змінні - зв'язок із проксі втрачається.
const state = reactive({ count: 0, name: 'Оля' });
const { count } = state; // просто число 0
state.count++; // count лишився 0
Як уникнути:
toRefsперетворює кожне поле наref, пов'язаний з початковим об'єктом:
const { count, name } = toRefs(state);
count.value++; // змінює state.count
toRef- для одного поля:const count = toRef(state, 'count').- Повертати з composables
ref-и, а неreactive, - тоді деструктуризація на місці виклику безпечна.
Props: props - теж реактивний об'єкт. До Vue 3.5 const { title } = defineProps(...) давав нереактивну змінну. З Vue 3.5 деструктуризація defineProps у <script setup> реактивна: компілятор сам перетворює title на props.title. Але передати таку змінну у функцію чи watch напряму не вийде - потрібен геттер: watch(() => title, ...).
Ще місця, де реактивність губиться:
- Заміна всього
reactive-об'єкта:state = reactive({...}). - Передача примітиву в функцію:
useSomething(state.count)передає число, а не реактивне джерело. ПередавайтеtoRef(state, 'count')чи геттер() => state.count.
Основа - Proxy і два хуки: track і trigger.
reactive()загортає об'єкт уProxy. Обробникgetвикликає track: запам'ятовує, що поточний «ефект» (рендер компонента,computed,watch) читає це поле.- Обробник
setвикликає trigger: знаходить усі ефекти, що читали поле, і планує їх перезапуск. refробить те саме через геттер і сеттер на.value- тому він працює з примітивами, які проксувати неможливо.
// спрощено
const state = new Proxy(target, {
get(obj, key) { track(obj, key); return obj[key]; },
set(obj, key, value) { obj[key] = value; trigger(obj, key); return true; },
});
Рендер компонента - теж ефект: під час рендеру він «підписується» рівно на ті поля, які прочитав шаблон. Зміна поля, якого шаблон не читає, рендер не запускає.
Планувальник і nextTick. Зміни не перемальовують DOM одразу. Vue збирає всі ефекти, які треба перезапустити, у чергу й виконує їх один раз в мікрозадачі. Десять змін поспіль - один рендер.
Звідси nextTick: одразу після зміни стану DOM ще старий.
items.value.push(newItem);
await nextTick();
listEl.value.lastElementChild.scrollIntoView(); // DOM уже оновлено
Наслідки для продуктивності:
- Глибока реактивність проксує вкладені об'єкти ліниво, при доступі. Великі незмінні дані (результат API на тисячі рядків) дешевше тримати в
shallowRef, де відстежується лише заміна.value. markRawпозначає об'єкт, який Vue ніколи не має робити реактивним: екземпляри сторонніх бібліотек, карти, редактори.
Прочитати - ще не значить знати
20 питань, по одному на екран, ~15 хв. Після завершення - розбір кожної помилки з посиланням на питання.