Observer 2
Observer#
ViewModel 의 실질적인 주인은 Binder(Observer) 이다
Binder 가 ViewModel 을 Observe
Binder(Observer) 는 ViewModel 의 리스너 이기도 하다
const Binder = class extends ViewModelListener {
#items = new Set;
#processors = {};
add(v, _ = type(v, BinderItem)) {
this.#items.add(v);
}
addProcessor(v, _0 = type(v, Processor)) {
this.#processors[v.cat] = v;
}
render(viewmodel, _ = type(viewmodel, ViewModel)) {
const processores = Object.entries(this.#processors);
this.#items.forEach(item => {
const vm = type(viewmodel[item.viewmodel], ViewModel), el = item.el;
processores.forEach(([pk, processor]) => {
Object.entries(vm[pk]).forEach(([k, v]) => {
processor.process(vm, el, k, v);
});
});
});
}
};
Visitor#
두 곳 이상의 알고리즘에 개입하는 외부에서 공급된 전략을 Visitor 라고 한다.
- render 에서 process 를 사용
- viewmodelUpdated 에서 process 를 사용
const Binder = class extends ViewModelListener {
#items = new Set;
#processors = {};
// 2) updated Set 에는 ViewModelValue 가 있다. subKey, cat, k, v..
viewmodelUpdated(updated) {
const items = {};
// Set (#items) 은 forEach 만 쓸 수 있기 때문에 obj 로 바꿨다.
this.#items.forEach(item => {
items[item.viewmodel] = [type(viewmodel[item.viewmodel], ViewModel), item.el];
});
updated.forEach(v => {
if (!items[v.subKey]) return;
const [vm, el] = items[v.subKey], processor = this.#processors[v.cat];
if (!el || !processor) return;
processor.process(vm, el, v.k, v.v);
});
}
add(v, _ = type(v, BinderItem)) {
this.#items.add(v);
}
addProcessor(v, _0 = type(v, Processor)) {
// ..
}
render(viewmodel, _ = type(viewmodel, ViewModel)) {
// ..
}
watch(viewmodel, _ = type(viewmodel, ViewModel)) {
// Binder 에서는 ViewModel 을 받아서, ViewModel 에게 구독을 신청한다
viewmodel.addListener(this);
this.render(viewmodel);
}
unwatch(viewmodel, _ = type(viewmodel, ViewModel)) {
viewmodel.removeListener(this);
}
};
client#
const scanner = new Scanner;
const binder = scanner.scan(document.querySelector('#target'));
binder.addProcessor(new (class extends Processor {
_process(vm, el, k, v) {
el.style[k] = v;
}
})('styles'));
binder.addProcessor(new (class extends Processor {
_process(vm, el, k, v) {
el.setAttribute(k, v);
}
})('attributes'));
binder.addProcessor(new (class extends Processor {
_process(vm, el, k, v) {
el[k] = v;
}
})('properties'));
binder.addProcessor(new (class extends Processor {
_process(vm, el, k, v) {
console.log('event', k, v, el);
el['on' + k] = e => v.call(el, e, vm);
}
})('events'));
const viewmodel = ViewModel.get({
isStop: false, changeContents() {
this.wrapper.styles.background = `rgb(${/*...*/})`;
this.contents.properties.innerHTML = Math;
// ...
;
},
wrapper: ViewModel.get({
styles: {width: '50%', background: '#ffa', cursor: 'pointer'}, events: {
click(e, vm) {
// 자식 vm 도 vm 객체이고
// vm 은 무조건 wrapper 이다. 어휴..
// 부모에 있는 것을 바꾸고 싶을땐 간접참조인 parent 에 있는 것을 바꾼다.
vm.parent.isStop = true;
console.log('click', vm);
},
},
}),
title: /* .. */ ,
contents: /* .. */ ,
});
binder.watch(viewmodel);
const f = _ => {
viewmodel.changeContents();
if (!viewmodel.isStop) requestAnimationFrame(f);
};
requestAnimationFrame(f);
- Binder 에서 render 를 호출하는 부분이 없어졌다.
- ViewModel 의 값을 바꾸는 부분만 존재한다.
전략, 탬플릿 메서드, 옵저브, 컴포짓 패턴만 잘써도 많은 문제를 해결할 수 있다.
다음시간에는 배열받아서 돔엘리먼트를 만들어주는것을 할 것이다.