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.