Kontynuacja lekcji poprzedniej, poznajemy wzorzec observer i staramy się rozwinąć nasz kod. Do dzieła.

Poprzednio napisaliśmy sobie coś takiego:

let cnt = {
    value: 0,
    id: 'reactiveVar',
    subscibedIds: ['numIpt'],

    update(newVal){
        document
        .getElementById(this.id)
        .setAttribute('value', newVal);

        this.subscibedIds.forEach(function(id){
            document
            .getElementById(id)
            .setAttribute('value', newVal);
        });
    }
};


const cntHandler = {
    set(obj, prop, value){
        if(prop !== 'value') 
            return Reflect.set(...arguments);
        obj.update(value);
        return Reflect.set(...arguments);
    }
}

Teraz rozwiniemy to sobie:

let cnt = {
    value: 0,
    id: 'reactiveVar',
    subscibedIds: ['numIpt'],

    update(newVal){
        document
        .getElementById(this.id)
        .setAttribute('value', newVal);

        this.subscibedIds.forEach(function(id){
            document
            .getElementById(id)
            .setAttribute('value', newVal);
        });
    },
    subscribe(id){
        this.subscibedIds.push(id);
    },
    unsubscribe(id){
        this.subscibedIds = this.subscibedIds.
        filter((val) => val !== id);
    }
};

cnt.unsubscribe('numIpt');
cnt.subscribe('numIpt');

Widać co robimy – pozwalamy dynamicznie dodawać i usuwać subskrybentów, którzy na update są również reaktywnie zmieniani. Jest to uproszczona wersja wzorca projektowego observer, który wygląda tak:

class Observable {
  constructor() {
    this.observers = [];
  }

  subscribe(func) {
    this.observers.push(func);
  }

  unsubscribe(func) {
    this.observers = this.observers.filter((observer) => observer !== func);
  }

  notify(data) {
    this.observers.forEach((observer) => observer(data));
  }
}

W jednym z kursów JSa widziałem fajny przykład tego wzorca, polecam przeanalizować, jeżeli coś jest niejasne:

function Candidate(name) {
    this.name = name;
}

Candidate.prototype.notify = function(offer) {
    console.log(this.name + " ma ofertę: " + offer);
}

function JobPortal() {
    this.observers = [];

    this.subscribe = function(observer) {
        this.observers.push(observer);
    }
    this.unsubscribe = function(observer) {
        var index = this.observers.findIndex( el => el === observer );
        this.observers.splice(index, 1);
    }
    this.addNewOffer = function(offer) {
        this.observers.forEach(element => element.notify(offer) );
    }
}

let jobPortal = new JobPortal();
let user1 = new Candidate("Kasia");
jobPortal.subscribe(user1);
let user2 = new Candidate("Adam");
jobPortal.subscribe(user2);

jobPortal.addNewOffer("Oferta pracy #1");
jobPortal.unsubscribe(user1);
jobPortal.addNewOffer("Oferta pracy #2")
jobPortal.unsubscribe(user2);

jobPortal.addNewOffer("Oferta #3");

We wzorcach projektowych zawsze polecam taką kolejność:

  • nauka konkretnego przykładu, dla którego wzorzec stanowi rozwiązanie
  • nauka teorii, dlaczego wzorzec taki a taki jest przydatny
  • nauka teorii, czym jest dany wzorzec

Każda inna kolejność to jest jakieś „masło maślane” i totalny koszmar. Uczymy się nie wiadomo czego, nie wiadomo po co, wszystko bardzo abstrakcyjne i bez jakiegoś oparcia się o konkretny przykład i po co to nam.

Ok, a teraz postaramy się o większą modularność. Robimy constructor function:

function ReactiveVariable(value, id, ...subscibedIds){
    this.value = value;
    this.id = id;
    this.subscibedIds = [...subscibedIds];
}

Dodajemy metody w prototypie, bo nie chcemy ich duplikować dla wszystkich obiektów:

ReactiveVariable.prototype.update = function(newVal){
    document
        .getElementById(this.id)
        .setAttribute('value', newVal);

        this.subscibedIds.forEach(function(id){
            document
            .getElementById(id)
            .setAttribute('value', newVal);
        });
}

ReactiveVariable.prototype.subscribe = function(id){
    this.subscibedIds.push(id);
}
ReactiveVariable.prototype.unsubscribe = function(id){
    this.subscibedIds = this.subscibedIds.
        filter((val) => val !== id);
}

I tworzymy za pomocą factory function nasz cnt i patrzymy, czy tak samo działa:

let cnt = new ReactiveVariable(0, 'reactiveVar', 'numIpt');

Ok, jest dobrze, więcej w następnej lekcji.