Популярные фреймворки, такие как Angular, Vue реализуют двустороннюю привязку под капотом. Но всегда полезно знать, что происходит внутри. В этой статье мы попытаемся реализовать двустороннюю привязку данных с использованием ванильного JS и шаблона наблюдателя.

Что ж, это не будет очень тяжелая реализация двустороннего связывания, но она наверняка даст вам представление о том, как это достигается в популярных фреймворках. То же самое можно реализовать разными способами.

Сначала давайте определим поле ввода, в которое мы добавим настраиваемый атрибут привязки данных, к которому мы будем выполнять привязку.

Обратите внимание на элемент span, к которому мы добавили то же значение атрибута. Это мы будем использовать для обновления значения этого элемента span.

Затем давайте напишем код для элементов, которые имеют этот атрибут привязки данных.

Обратите внимание, что здесь мы будем брать значение из поля ввода (наше представление) и обновлять значение элемента span (которое привязано к модели). Теперь переходит к основной части реализации паттерна наблюдателя.

Позвольте мне попробовать шаг за шагом провести вас по шаблону. В нашем шаблоне наблюдателя есть три основных метода: updateState - для обновления локального состояния (нашей модели), notifyObservers (для уведомления всех наблюдателей) и addObserver - для добавления наблюдателей в наш шаблон.

Что в конечном итоге произойдет, так это то, что мы вызовем метод состояния обновления, который обновит состояние, а затем уведомит всех наблюдателей, каждый раз передавая им обновленное состояние.

Давайте воспользуемся этим шаблоном в нашей небольшой демонстрации.

const dataBindObserver = new Observer();

Итак, здесь мы обновляем состояние наблюдателя с текущим значением ввода. Мы заботимся о том, чтобы каждое входное значение сохранялось, используя уникальный атрибут привязки данных, который мы ему передали.

Теперь давайте добавим наблюдателя для прослушивания изменений внутри состояния.

Состояние, которое мы получим, будет обновленным состоянием с измененными значениями входных данных. Затем мы можем просто изменить содержимое любого элемента, который, как ожидается, отразит это изменение.

Теперь давайте добавим функции для изменения нашей модели и, в свою очередь, изменения вида. Предположим, наша модель (в данном случае состояние) изменяется из-за некоторого действия пользователя, такого как щелчок кнопки или некоторый вызов API и т. Д. Наш наблюдатель должен быть достаточно умным, чтобы обнаруживать изменение, а затем отражать это изменение в нашем представлении. Давайте добавим кнопку в наш html и прикрепим к ней событие нажатия.

<div><button onclick="resetAll()">Reset all</button></div>
const resetAll = () => {
  dataBindObserver.updateState({});
};

Здесь мы обновляем состояние с помощью пустого объекта. Давайте изменим нашего наблюдателя, чтобы обрабатывать такие изменения.

const initialState = {
  name: 'This is the  initial state.',
  surname: 'This is the initial state.'
};
const Observer = function() {
   ...
  this.updateState = updateObj => {
    if (Object.keys(updateObj).length === 0) {
      this.state = {
         ...initialState
      };
    } else {
       Object.keys(updateObj).map(key => {
          this.state = {
             ...this.state,
             [key]: updateObj[key]
          };
       });
    }
   this.notifyObservers();
 };
   ...
}

И, наконец, когда мы наблюдаем за изменениями состояния, нам также необходимо добавить наблюдателей на наши входные элементы.

...
if (!element.type) {
  dataBindObserver.addObserver(state => {
    if (state[stateVar]) {
      element.innerHTML = state[stateVar];
    }
  });
} else {
   dataBindObserver.addObserver(state => {
     if (state[stateVar]) {
       element.value = state[stateVar];
     }
   });
}
...

Вот небольшая запись рабочего примера.

Здесь я прилагаю репозиторий github для быстрого ознакомления.

Если вы найдете эту статью полезной, хлопайте в ладоши или поделитесь своим мнением в разделе комментариев.