Создание ванильного JavaScript signal() с Proxy

Источник: «Creating a vanilla JavaScript signal() with Proxies»
Вчера мы рассмотрели ванильные JS 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 с этим примером.

See the Pen

Дополнительные материалы

Предыдущая Статья

Введение в JavaScript Proxy

Следующая Статья

Новые альтернативы innerHTML