Przejdź do głównej treści
Grafika przedstawia ukryty obrazek

Jak zrealizować dwukierunkowe wiązanie danych MVVM w Vanilla JS?

Jak zrealizować dwukierunkowe wiązanie danych MVVM w Vanilla JS?

Wzorzec projektowy MVVM (Model-View-ViewModel) umożliwia rozdzielenie logiki aplikacji od jej warstwy prezentacji. W kontekście aplikacji webowych pomaga zarządzać interakcjami użytkownika i aktualizacjami danych w sposób czytelny oraz utrzymywany. W odróżnieniu od popularnych frameworków takich jak Vue.js czy Angular, możemy wdrożyć ten wzorzec również za pomocą czystego JavaScriptu (Vanilla JS).

Główne elementy MVVM

  1. Model — Reprezentuje dane oraz logikę biznesową aplikacji.
  2. View — Odpowiada za interfejs użytkownika (HTML).
  3. ViewModel — Pośrednik między Modelem a Widokiem, zarządzający synchronizacją danych i zdarzeniami.

Poniżej przedstawię krok po kroku implementację tego wzorca w Vanilla JS.

1. Definicja modelu

Model będzie prostym obiektem przechowującym dane aplikacji. Dodamy do niego funkcjonalność subskrypcji i powiadamiania obserwatorów o zmianach danych:

class Model {
  constructor(initialData = {}) {
    this.data = initialData;
    this.listeners = [];
  }

  subscribe(listener) {
    this.listeners.push(listener);
  }

  notify() {
    this.listeners.forEach(listener => listener(this.data));
  }

  set(property, value) {
    if (this.data[property] !== value) {
      this.data[property] = value;
      this.notify();
    }
  }

  get(property) {
    return this.data[property];
  }
}

2. Tworzenie ViewModelu

ViewModel zarządza komunikacją między Modelem a Widokiem. Będzie on subskrybował zmiany Modelu i aktualizował Widok.

class ViewModel {
  constructor(model) {
    this.model = model;
    this.bindings = {};
  }

  bind(property, element, eventType = null) {
    // Aktualizacja View na zmiany Modelu
    element.value = this.model.get(property) || "";
    element.textContent = this.model.get(property) || "";

    this.model.subscribe(data => {
      if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
        element.value = data[property];
      } else {
        element.textContent = data[property];
      }
    });

    // Aktualizacja Modelu na zmiany View (dwukierunkowe wiązanie)
    if (eventType) {
      element.addEventListener(eventType, e => {
        this.model.set(property, e.target.value);
      });
    }
  }
}

3. Implementacja Widoku

HTML może zawierać elementy, które będziemy wiązać z Modelem:

<div>
  <label for="name">Name:</label>
  <input type="text" id="name">
</div>
<p>Name: <span id="displayName"></span></p>

4. Integracja komponentów

// Inicjalizacja Modelu
const model = new Model({ name: 'John Doe' });

// Inicjalizacja ViewModelu
const viewModel = new ViewModel(model);

// Powiązanie danych
const nameInput = document.getElementById('name');
const displayName = document.getElementById('displayName');

viewModel.bind('name', nameInput, 'input');
viewModel.bind('name', displayName);

5. Gotowy HTML z JavaScript

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vanilla MVVM</title>
  </head>
  <body>
    <div>
      <label for="name">Name:</label>
      <input type="text" id="name">
    </div>
    <p>Name: <span id="displayName"></span>
    </p>
    <script>
      class Model {
        constructor(initialData = {}) {
          this.data = initialData;
          this.listeners = [];
        }
        subscribe(listener) {
          this.listeners.push(listener);
        }
        notify() {
          this.listeners.forEach(listener => listener(this.data));
        }
        set(property, value) {
          if (this.data[property] !== value) {
            this.data[property] = value;
            this.notify();
          }
        }
        get(property) {
          return this.data[property];
        }
      }
      class ViewModel {
        constructor(model) {
          this.model = model;
          this.bindings = {};
        }
        bind(property, element, eventType = null) {
          // Aktualizacja View na zmiany Modelu
          element.value = this.model.get(property) || "";
          element.textContent = this.model.get(property) || "";
          this.model.subscribe(data => {
            if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
              element.value = data[property];
            } else {
              element.textContent = data[property];
            }
          });
          // Aktualizacja Modelu na zmiany View (dwukierunkowe wiązanie)
          if (eventType) {
            element.addEventListener(eventType, e => {
              this.model.set(property, e.target.value);
            });
          }
        }
      }
      // Inicjalizacja Modelu
      const model = new Model({
        name: 'John Doe'
      });
      // Inicjalizacja ViewModelu
      const viewModel = new ViewModel(model);
      // Powiązanie danych
      const nameInput = document.getElementById('name');
      const displayName = document.getElementById('displayName');
      viewModel.bind('name', nameInput, 'input');
      viewModel.bind('name', displayName);
    </script>
  </body>
</html>

6. Działający przykład

Name:

7. Rozszerzenia

Możesz rozbudować powyższą implementację o:

  • Obsługę walidacji danych w ViewModelu.
  • Większą liczbę dwukierunkowych wiązań.
  • Obsługę dynamicznego dodawania elementów do DOM.
  • Lepsze zarządzanie wydajnością przez debouncing przy wiązaniu zdarzeń.

Podsumowanie

Implementacja wzorca MVVM w Vanilla JS wymaga ręcznego zarządzania subskrypcjami i synchronizacją danych, ale daje pełną kontrolę nad logiką aplikacji. Wdrożenie tego wzorca może być świetnym ćwiczeniem pozwalającym zrozumieć, jak działają popularne frameworki frontendowe.

2 lutego 2025 15

Kategorie

programowanie

Tagi

javascript

Dziękujemy!
()

Powiązane wpisy


Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.