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.