Создание ванильного 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 с этим примером.