Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що таке Proxy і Reflect і як на них побудована реактивність Vue?

Proxy - обгортка над об'єктом, що перехоплює базові операції з ним: читання й запис властивостей, видалення, перевірку in, перебір ключів, виклик функції. Перехоплювачі називають «пастками» (traps).

const target = { name: 'Оля' };

const logged = new Proxy(target, {
  get(obj, key, receiver) {
    console.log(`читання ${String(key)}`);
    return Reflect.get(obj, key, receiver);
  },
  set(obj, key, value, receiver) {
    console.log(`запис ${String(key)} = ${value}`);
    return Reflect.set(obj, key, value, receiver);
  },
});

logged.name;       // читання name
logged.age = 30;   // запис age = 30

Reflect - набір функцій, що виконують ту саму базову операцію «за замовчуванням». У пастці його використовують, щоб після власної логіки виконати стандартну поведінку правильно - зокрема з параметром receiver, від якого залежить this для гетерів.

Пастки: get, set, has (in), deleteProperty, ownKeys (Object.keys, for...in), apply (виклик функції), construct (new), defineProperty, getPrototypeOf та інші - для кожної внутрішньої операції мови.

Реактивність Vue 3:

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);                    // запам'ятати: поточний ефект залежить від key
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      trigger(target, key);                  // перезапустити ефекти, що залежать від key
      return result;
    },
  });
}

Під час рендеру компонента кожне читання реактивної властивості реєструє залежність. Зміна властивості перезапускає лише ті рендери й computed, що від неї залежать.

Чому Vue 3 перейшов з гетерів/сетерів (Vue 2) на Proxy:

  • нові властивості відстежуються автоматично - у Vue 2 доводилося викликати Vue.set;
  • індекси масиву й length - зміна items[3] = x теж реактивна;
  • Map, Set - через перехоплення їхніх методів;
  • не треба заздалегідь обходити весь об'єкт: вкладені об'єкти обгортаються ліниво, при доступі.

Інші застосування: валідація записів, значення за замовчуванням для відсутніх ключів, логування й налагодження, «віртуальні» об'єкти (клієнт API, де api.users.list() будує запит з назв властивостей), моки в тестах.

Обмеження:

  • тотожність: proxy !== target. Порівняння реактивного об'єкта з оригіналом (toRaw у Vue) - часте джерело плутанини;
  • приватні поля # і внутрішні слоти (Map, Date) не проходять крізь проксі: методи, викликані на проксі, отримують this = проксі й падають. Тому для вбудованих колекцій Vue має окремі обробники;
  • продуктивність: кожна операція йде через пастку - для гарячих циклів по великих структурах це відчутно (звідси shallowRef, markRaw);
  • інваріанти: пастка не може «збрехати» про незмінювану властивість (наприклад, заморожену) - рушій кине TypeError.

Докладніше в документації: Proxy

Схожі питання