habrahabr

defi.js — реактивная библиотека, основанная на Object.defineProperty

  • понедельник, 21 января 2019 г. в 19:47:06
https://habr.com/ru/company/matreshka/blog/436778/
  • Блог компании Matreshka.js
  • JavaScript


defi.js

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.


Спасибо, что прочли до конца. Всем хорошего дня.