viewmodelUpdated 부분 개선
const Binder = class extends ViewModelListener {
#items = new Set();
#processors = {};
...
viewmodelUpdated(viewmodel, updated) { // 전역에서 끌고오던 부분을 인자로 수정
const items = {};
this.#items.forEach((item) => {
items[item.viewmodel] = [
type(viewmodel[item.viewmodel], ViewModel),
item.el
];
});
updated.forEach(({ subKey, category, key, value }) => {
if (!items[subKey]) return;
const [vm, el] = items[subKey];
const processor = this.#processors[category];
if (!el || !processor) return;
processor.process(vm, el, key, value);
});
}
};
- Binder에 들어올수 있는 viewmodel은 notify 를 통해 들어오는 데 ViewModelSubject와 이를 상속 받은ViewModel 둘다 가능
- 우리가 원하는건 viewmodel 은 ViewModel 이어야한다
const ViewModelSubject = class extends ViewModelListener {
// ... 생략
// 상속받은 애가 this.notifyTarget getter으로 뭘 보낼지 결정할 수 있게함
notify () { this.#listeners.forEach(v => v.viewmodelUpdated(this.notifyTarget, this.#info)) }
get notifyTarget () { throw 'must be override!' } // ViewModel에게 위임한다.
}
const ViewModel = class extends ViewModelSubject {
// ... 생략
get notifyTarget () { return this } // notifyTarget을 호출하면 ViewModel을 보낸다.
}
const Binder = class extends ViewModelListener {
// .. 생략
viewmodelUpdated(target, updated,
_ = type(target, ViewModel)){ // target은 ViewModel 이여야 한다.
const items = {}
this.#items.forEach(({ vmName, el }) => {
items[vmName] = [type(target[vmName], ViewModel), el]
})
updated.forEach(({ subKey, category, k, v }) => {
if (!items[subKey]) return
const [vm, el] = items[subKey], processor = this.#processors[category]
if (!el || !processor) return
processor.process(vm, el, k, v)
})
}
}
- 추상 계층을 분리하게 될 경우, 어떠한 문제가 생겼을 때 각각의 계층에서 해결하도록 만드는 것을 지향해야 한다.
getter
Object API, Proxy, Symbol 정리
Processor 개선
[문제점]
데이터 구조가 굳어 져서 처리기의 동적인 형태추가 로직 막은 구조
Binder
안에 외부에서 등록한 Processor
들을 돌리면서 동작 중이다
- 반면,
ViewModel
에서는 이와 다르게 하드 코딩을 하고 있는 문제점이 있다
ViewModel
로 인해 등록해서 무한히 Processor
확장 시키도록 설계한 부분이 의미없게된다
- 처리기는 데이터 구조와 연동된다
// Binder의 Render 부분
const Binder = class extends ViewModelListener {
// ... 생략
render (viewmodel, _ = type(viewmodel, ViewModel)) {
const processores = Object.entries(this.#processors)
this.#items.forEach(({ vmName, el }) => {
const vm = type(viewmodel[vmName], ViewModel)
processores.forEach(([pk, processor]) => {
Object.entries(vm[pk]).forEach(([k, v]) => {
processor.process(vm, el, k, v)
})
})
})
}
}
// ViewModel
const ViewModel = class extends ViewModelSubject {
// ... 생략
constructor(data, _ = type(data, 'object')) {
super();
Object.entries(data).forEach(([k, v]) => {
if('styles,attributes,properties'.includes(k)) {
if(!v || typeof v != 'object') throw `invalid object k: ${k}, v:${v}`
this[k] = ViewModel.define(this, k, v)
} else {
Object.defineProperty(this, k, ViewModel.descriptor(this, '', k, v))
if (v instanceof ViewModel) {
v._setParent(this, k)
}
}
})
Object.seal(this)
}
}
// Client 코드
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) { el[`on${k}`] = e => v.call(el, e, vm) }
}('events'))
또, 데이터 형식이 자유롭지 않고 코드로 미리 다 정해버린 구조
- styles가 key면 Object가 무조건 올것이다 이런 느낌의 코드들
- 코드를 읽어야지 데이터 형식을 알고 보내고 이런 번거로운 상황 반복된다
const ViewModel = class extends ViewModelSubject {
...
static descriptor = (vm, category, k, v) => ({
enumerable: true,
get: () => v,
set: (newV) => {
v = newV;
vm.add(new ViewModelValue(vm.subKey, category, k, v));
}
});
static defineProperties = (vm, category, obj) =>
Object.defineProperties(
obj,
Object.entries(obj).reduce(
(r, [k, v]) => ((r[k] = ViewModel.descriptor(vm, category, k, v)), r),
{}
)
);
constructor(data, _ = type(data, "object")) {
super();
Object.entries(data).forEach(([key, value]) => {
if ("styles,attributes,properties".includes(key)) {
this[key] = ViewModel.defineProperties(this, key, value);
} else {
Object.defineProperty(
this,
key,
ViewModel.descriptor(this, "", key, value)
);
if (value instanceof ViewModel) {
value.setParent(this, key);
}
}
});
Object.seal(this);
}
....
};
...
const wrapper = ViewModel.get({
styles: { width: "50%", background: "#ffa", cursor: "pointer" },
events: {
click(e, vm) {
vm.parent.isStop = true;
}
}
});
자유롭게 기술하기 위한 방법