defi.js — это библиотека, включающая в себя дюжину функций, которые добавляют интересные фичи любым JavaScript объектам с помощью геттеров и сеттеров.
Гифка для привлечения внимания (3.5МБ)
Репозиторий
В качестве Hello World создадим небольшой виджет, состоящий из поля имени, фамилии и приветствия (демо).
<input class="first">
<input class="last">
<output class="greeting"></output>
// данные по умолчанию
const obj = {
first: 'John',
last: 'Doe'
};
// слушаем изменения в свойствах first и last
// если произошло изменение, сообщим об этом в консоли
defi.on(obj, 'change:first', () => console.log('First name is changed'));
defi.on(obj, 'change:last', () => console.log('Last name is changed'));
// автоматически генерируем приветствие (свойство greeting) каждый раз,
// когда first или last изменились
defi.calc(obj, 'greeting', ['first', 'last'], (first, last) => `Hello, ${first} ${last}`);
// объявляем двусторонний байндинг между свойствами
// и соответствующими элементами на странице
defi.bindNode(obj, {
first: '.first',
last: '.last',
greeting: '.greeting'
});
В итоге, если first
или last
изменились, обработчики события сообщают об этом в консоли, свойство greeting
автоматически обновляется, а его элемент получает новое значение (по умолчанию, "Hello, John Doe"). Это случается каждый раз, когда свойства меняются, причем не имеет значения, каким образом. Можно установить значение с помощью кода obj.first = 'Jane'
, либо изменив значение поля, и все остальные изменения произойдут автоматически.
В случае, если есть необходимость синхронизации свойства объекта и innerHTML
произвольного HTML элемента (в примере мы использовали тэг output
), в функцию bindNode
нужно передать так называемый "байндер", который отвечает за то, как синхронизировать состояние элемента и свойства объекта. По умолчанию bindNode
не знает, как работать с нодами, не являющимися элементами форм.
const htmlBinder = {
setValue: (value, binding) => binding.node.innerHTML = value,
};
// изменение свойства obj.greeting обновит innerHTML любого элемента
defi.bindNode(obj, 'greeting', '.greeting', htmlBinder)
Кроме этого, можно воспользоваться html
из библиотеки common-binders (это коллекция байндеров общего назначения).
const { html } = require('common-binders');
// изменение свойства obj.greeting обновит innerHTML любого элемента
defi.bindNode(obj, 'greeting', '.greeting', html())
Методы API
Подробную документацию ко всем методам, доступные вариации вызова, флаги и др. можно прочесть на defi.js.org. Стоит упомянуть, что кроме методов ниже, у defi.js есть библиотека для роутинга: defi-router.
- bindNode — Связывает свойство объекта и DOM узел для двустороннего байндинга.
// обычное использование
// (для стандартных HTML5 элементов форм, см. defaultBunders)
defi.bindNode(obj, 'myKey', '.my-element');
// кастомный байндинг
defi.bindNode(obj, 'myKey', '.my-element', {
// событие, которое говорит об изменении элемента
// (можно использовать функцию для не-DOM событий)
on: 'click',
// как извлечь текущее состояние элемента?
getValue: ({ node }) => someLibraryGetValue(node),
// как установить состояние элемента при изменении свойства?
setValue: (v, { node }) => someLibrarySetValue(node, v),
// как инициализировать элемент (библиотеку или виджет)?
// это можно сделать любым способом
// но 'initialize' добавляет немного синтаксического сахара
initialize: ({ node }) => someLibraryInit(node),
});
obj.myKey = 'some value'; // обновит элемент
- calc — Создает зависимость одного свойства объекта от других свойств (в том числе, из других объектов).
defi.calc(obj, 'a', ['b', 'c'], (b, c) => b + c);
obj.b = 1;
obj.c = 2;
console.log(obj.a); // 3
- mediate — Модифицирует значение свойства при его изменении.
defi.mediate(obj, 'x', value => String(value));
obj.x = 1;
console.log(obj.x); // "1"
console.log(typeof obj.x); // "string"
- on — Добавляет обработчик событий. На сайте с документацией есть небольшая статья, описывающая все возможные события.
defi.on(obj, 'change:x', () => {
alert(`obj.x now equals ${obj.x}`);
});
obj.x = 1;
- off — Удаляет обработчик события.
defi.off(obj, 'change:x bind');
- trigger — Триггерит событие.
defi.on(obj, 'foo bar', (a, b, c) => {
alert(a + b + c);
});
defi.trigger(obj, 'foo', 1, 2, 3); // вызывает alert(6)
- unbindNode — Отключает связывание элемента и DOM узла.
defi.bindNode(obj, 'myKey', '.my-element');
defi.unbindNode(obj, 'myKey', '.my-element');
- bound — Возвращает DOM элемент, связанный с заданным свойством.
defi.bindNode(obj, 'myKey', '.my-element');
const node = defi.bound(obj, 'myKey'); // вернет document.querySelector('.my-element')
- chain — Используется для цепочечного вызова функций defi.js.
defi.chain(obj)
.calc('a', 'b', b => b * 2)
.set('b', 3)
.bindNode('c', '.node');
- defaultBinders — Массив функций, возвращающих соответствующий байндер или undefined. Позволяет
bindNode
связывать кастомные элементы и свойства без явного указания байндера.
defi.defaultBinders.unshift(element => {
// если у элемента есть класс "foo"
if(element.classList.contains('foo')) {
// значит, нужно использовать этот байндер
return {
on: ...,
getValue: ...,
setValue: ...
};
}
});
// ...
defi.bindNode(obj, 'myKey', '.foo.bar');
- lookForBinder — Возвращает байндер, соответствующий элементу, если таковой вернула одна из функций
defaultBinders
.
const element = document.createElement('input');
element.type = 'text';
console.log(defi.lookForBinder(element));
- remove — Удаляет свойство объекта и соотвествующие обработчики событий.
defi.remove(obj, 'myKey');
- set — Устанавливает свойство объекта, как и сеттер, но дает возможность передать какие-нибудь данные в обработчик события
change:KEY
. Так же можно сделать установку значения свойства "тихим", т. е. не вызыватьchange:KEY
вовсе.
defi.set(obj, 'myKey', 3, { silent: true });
defi.js — это переработанный и упрощенный хард-форк фреймворка Matreshka.js, который включал в себя рендеринг массивов, несколько классов и больше методов. Некоторые методы, которые потенциально могли бы попасть в defi.js были заменены на опции к другим методам, например, вместо once
и onDebounce
можно использовать метод on
, передав опции once: true
или debounce: number
.
Спасибо, что прочли до конца. Всем хорошего дня.
Комментарии (13)
artalar
21.01.2019 10:23Крайне не рекомендую использовать паттерн «заместитель» в проде, апи к любому функционалу должно быть явным и не должно быть скрыто за стандартными конструкциями ЯП.
youtu.be/dCXvQkvSyQg?t=1907Sirion
21.01.2019 13:33+1Должно кому?
artalar
21.01.2019 13:34Семантике
Sirion
21.01.2019 14:07Для семантики будет лучше, если мы будем на каждый вызов писать десяток лишних символов, относящихся не к тому, что мы хотим сделать, а к тому, как мы хотим это сделать?
artalar
21.01.2019 14:16«каждый», «десятки» — как-то очень абстрактно. Чтение \ запись и подписка \ обновление — это разные процессы и описывать их нужно по разному
Sirion
21.01.2019 14:26+1Весь смысл абстракции — в том, чтобы скрыть какие-то детали реализации. Допустим, наследование в JS скрывает, вызываем мы метод объекта или какого-то из прототипов. Наследование тоже зло?
artalar
21.01.2019 14:43Отвечает Егор Бугаенко =D
Sirion
21.01.2019 14:58Если это отсылка к видео, линк на которое вы привели в начале ветки, то, во-первых, я его не могу сейчас посмотреть по техническим причинам. Отсюда плавно вытекает «во-вторых»: использование видео там, где достаточно текста — моветон, от которого недалеко до вставки кода картинками.
artalar
21.01.2019 15:25Нет, Егор Бугаенко — это довольно известная личность (хоть и из мира Java). И он выступает «против» наследования. Рекомендую ознакомится с его докладами и статьями, ЯП в них играет не самую важную роль.
P.S. он так же выступает против геттеров \ сеттеров, но по другой причине… Подробнее где-то в этом докладе youtu.be/lfdAwl3-X_c
justboris
21.01.2019 23:12+1Спасибо за ссылку!
Тоже видел проблемы когда люди злоупотребляли геттерами/сеттерами. Теперь буду знать как этот паттерн называется.
sanchezzzhak
22.01.2019 17:19-1До моделей состоящие из одного obj сгодится и то трижды подумать придется перед использованием, есть proxy негативное решение, для большого списка obj как у меня 500к+, я бы точно не использовал подобное.
Serg_de_Adelantado
Осталось добавить рендер и будет Vue :) Правда, она потихоньку на Proxy мигрирует.
Kr0nic
и Virtual DOM…