Создание ванильного JavaScript signal() с Proxy
Пример
Представим, что есть объект cart ({}).
let cart = {};Каждый раз, когда он обновляется, код в разных местах должен знать, что он был изменён, знать, что изменилось, и выполнять какие-то действия в ответ.
// Когда это произойдёт, в ответ автоматически должен быть выполнен другой код
cart.shirt = {
size: 'medium',
quantity: 1
};Proxy идеально подходят для этого!
Создание функции signal() с помощью Proxy
Сначала создадим функцию signal(), принимающую значение данных для создания Proxy.
Используем обычный объект ({}) в качестве значения по умолчанию, если таковой не указан. Также необходимо, чтобы каждый объект сигнала имел уникальное имя или идентификатор. Для этого передадим переменную name.
function signal (data = {}, name = '') {
// ...
}Далее создадим функцию handler(), возвращающую наш объект обработчик. Это необходимо для работы с вложенными массивами и объектами.
function signal (data = {}, name = '') {
/**
* Создание объекта обработчика Proxy
* @param {Object} data Объект данных
* @param {String} name Название сигнала
* @return {Object} Объект обработчик
*/
function handler (data, name) {
return {
get (obj, prop) {
if (key === '_isProxy') return true;
let nested = ['[object Object]', '[object Array]'];
let type = Object.prototype.toString.call(obj[key]);
if (nested.includes(type) && !obj[key]._isProxy) {
obj[prop] = new Proxy(obj[prop], handler(name, data));
}
return obj[prop];
},
set (obj, prop, value) {
if (obj[prop] === value) return true;
obj[prop] = value;
return true;
},
deleteProperty (obj, prop) {
delete obj[prop];
return true;
}
};
}
}Теперь можно создать и вернуть new Proxy().
function signal (data = {}, name = '') {
// ...
// Создание new Proxy
return new Proxy(data, handler(data, name));
}Запуск пользовательского события
Давайте добавим функцию emit() к функции signal(), которая вызывает пользовательское событие.
Передадим name сигнала, а также объект detail с подробной информацией о произошедших изменениях.
/**
* Создание пользовательского события
* @param {String} name Уникальное имя сигнала
* @param {*} detail Любые подробности, которые можно передать вместе с событием
*/
function emit (name, detail = {}) {
// Создание нового события
let event = new CustomEvent(`signal:${name}`, {
bubbles: true,
detail: detail
});
// Отправка события
return document.dispatchEvent(event);
}Теперь в handler() можно вызывать emmit(), когда данные устанавливаются или удаляются.
Для подробностей включим prop, который был изменён, его value и action, указывающее, как он изменился.
/**
* Создание объекта обработчика Proxy
* @param {Object} data Объект данных
* @param {String} name Название сигнала
* @return {Object} Объект обработчик
*/
function handler (data, name) {
return {
get (obj, prop) {
if (key === '_isProxy') return true;
let nested = ['[object Object]', '[object Array]'];
let type = Object.prototype.toString.call(obj[key]);
if (nested.includes(type) && !obj[key]._isProxy) {
obj[prop] = new Proxy(obj[prop], handler(name, data));
}
return obj[prop];
},
set (obj, prop, value) {
if (obj[prop] === value) return true;
obj[prop] = value;
emit(name, {prop, value, action: 'set'});
return true;
},
deleteProperty (obj, prop) {
delete obj[prop];
emit(name, {prop, value: obj[prop], action: 'delete'});
return true;
}
};
}Использование signal()
Теперь можно создать объект cart как signal(), например, так…
let cart = signal({}, 'cart');Изменения в нём можно прослушивать следующим образом…
document.addEventListener('signal:cart', function (event) {
console.log(event.detail);
});И каждый раз, при обновлении cart, будет возникать событие.
cart.shirt = {
size: 'medium',
quantity: 1
};
cart.pants = {
size: 32,
quantity: 2
};
delete cart.pants;В этом примере данные выводятся в консоль. Можно включить панель Инспектор и перейти во вкладку консоль и увидеть результат выполнения скрипта там. Или перейти на сайт CodePen с этим примером.