Основа - 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 ніколи не має робити реактивним: екземпляри сторонніх бібліотек, карти, редактори.