コード断片86オブジェクト向けJavaScript-第2部第3部(ステップ31)
MVVMの適用を試みる
1. ViewModel
const ViewModel = class {
static #private = Symbol(); // #을 통해 private로 외부에서 접근불가
static get(data) {
return new ViewModel(this.#private, data); //this는 클래스를 나타냄
}
styles = {};
attributes = {};
properties = {};
events = {};
constructor(checker, data) {
if (checker != ViewModel.#private) throw 'useViewModel.get()!';
Object.entries(data).forEach(([k, v]) => {
switch (k) {
case'styles':
this.styles = v;
break;
case'attributes':
this.attributes = v;
break;
case'properties':
this.properties = v;
break;
case'events':
this.events = v;
break;
default:
this[k] = v;
}
});
Object.seal(this);
}
};
2. Binder
const BinderItem = class {
el;
viewmodel;
constructor(el, viewmodel, _0 = type(el, HTMLElement), _1 = type(viewmodel, 'string')) {
this.el = el;
this.viewmodel = viewmodel;
Object.freeze(this); // 불변객체화
}
};
const Binder = class {
#items = new Set;
add(v, _ = type(v, BinderItem)) {
this.#items.add(v);
}
render(viewmodel, _ = type(viewmodel, ViewModel)) {
this.#items.forEach(item => {
const vm = type(viewmodel[item.viewmodel], ViewModel), el = item.el;
Object.entries(vm.styles).forEach(([k, v]) => el.style[k] = v);
Object.entries(vm.attributes).forEach(([k, v]) => el.setAttribute(k, v));
Object.entries(vm.properties).forEach(([k, v]) => el[k] = v);
Object.entries(vm.events).forEach(([k, v]) => el['on' + k] = e => v.call(el, e, viewmodel)); // this 를 el 로 바인딩
});
}
};
対応するBinderで必要な機能を追加または処理する機能.(制御局)3. Scanner
const Scanner = class {
scan(el, _ = type(el, HTMLElement)) {
const binder = newBinder;
this.checkItem(binder, el);
const stack = [el.firstElementChild];
let target; // 임시변수
while (target = stack.pop()) {
this.checkItem(binder, target);
if (target.firstElementChild) stack.push(target.firstElementChild);
if (target.nextElementSibling) stack.push(target.nextElementSibling);
}
return binder;
}
checkItem(binder, el) {
const vm = el.getAttribute('data-viewmodel');
if (vm) binder.add(new BinderItem(el, vm));
}
};
4. Client
const viewmodel = ViewModel.get({
wrapper: ViewModel.get({styles: {width: '50%', background: '#ffa', cursor: 'pointer'}}),
title: ViewModel.get({properties: {innerHTML: 'Title'}}),
contents: ViewModel.get({properties: {innerHTML: 'Contents'}}),
});
const scanner = new Scanner;
const binder = scanner.scan(document.querySelector('#target'));
binder.render(viewmodel);
クライアント変換
const viewmodel = ViewModel.get({
isStop: false,
changeContents() {
this.wrapper.styles.background = `rgb(${parseInt(Math.random() * 150) + 100},${...},${...})`;
this.contents.properties.innerHTML = Math.random().toString(16).replace('.', '');
}, wrapper: ViewModel.get({
styles: {width: '50%', background: '#ffa', cursor: 'pointer'}, events: {
click(e, vm) {
vm.isStop = true;
},
},
}),
// ...
});
const f = _ => {
viewmodel.changeContents();
binder.render(viewmodel);
if (!viewmodel.isStop) requestAnimationFrame(f);
};
requestAnimationFrame(f);
完全なコード
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MVVM</title>
</head>
<body>
<section id="target" data-viewmodel="wrapper">
<h2 data-viewmodel="title"></h2>
<section data-viewmodel="contents"></section>
</section>
<script>
const type = (target, type) => {
if (typeof type == "string") {
if (typeof target != type) throw `invalid type ${target} : ${type}`
} else if (!(target instanceof type)) {
throw `invalid type ${target} : ${type}`
}
return target;
}
const ViewModel = class {
static #private = Symbol()
static get (data) {
return new ViewModel(this.#private, data)
}
styles = {}; attributes = {}; properties = {}; events = {};
constructor(checker, data) {
if (checker != ViewModel.#private) throw 'use ViewModel.get()!'
Object.entries(data).forEach(([k, v]) => {
switch (k) {
case 'styles': this.styles = v; break;
case 'attributes': this.attributes = v; break;
case 'properties': this.properties = v; break;
case 'events': this.events = v; break;
default: this[k] = v;
}
});
Object.seal(this); // Value를 바꿀 순 있지만 Key를 추가할 순 없다.
}
}
const BinderItem = class {
el; viewmodel;
constructor (el, viewmodel, _0 = type(el, HTMLElement), _1 = type(viewmodel, 'string')) {
this.el = el
this.viewmodel = viewmodel
Object.freeze(this)
}
}
const Binder = class {
#items = new Set()
add (v, _ = type(v, BinderItem)) { this.#items.add(v) }
render (viewmodel, _ = type(viewmodel, ViewModel)) {
this.#items.forEach(item => {
const vm = type(viewmodel[item.viewmodel], ViewModel), el = item.el
Object.entries(vm.styles).forEach(([k, v]) => el.style[k] = v)
Object.entries(vm.attributes).forEach(([k, v]) => el.attribute[k] = v)
Object.entries(vm.properties).forEach(([k, v]) => el[k] = v)
Object.entries(vm.events).forEach(([k, v]) => el[`on${k}`] = e => v.call(el, e, viewmodel))
})
}
}
const Scanner = class {
scan (el, _ = type(el, HTMLElement)) {
const binder = new Binder();
this.checkItem(binder, el)
const stack = [el.firstElementChild]
// HTML 전체에 대한 순회
let target
while (target = stack.pop()) {
this.checkItem(binder, target)
if (target.firstElementChild) stack.push(target.firstElementChild)
if (target.nextElementSibling) stack.push(target.nextElementSibling)
}
return binder;
}
checkItem (binder, el) {
const vm = el.getAttribute('data-viewmodel')
if (vm) binder.add(new BinderItem(el, vm))
}
}
const scanner = new Scanner()
const binder = scanner.scan(document.querySelector('#target'))
const getRandom = () => parseInt(Math.random() * 150) + 100
const viewmodel2 = ViewModel.get({
isStop: false,
changeContents () {
this.wrapper.styles.background = `rgb(${getRandom()},${getRandom()},${getRandom()})`
this.contents.properties.innerHTML = Math.random().toString(16).replace('.', '')
binder.render(viewmodel2)
// viewmodel을 갱신하면, binder가 viewmodel을 view에 rendering 한다.
// 즉, '인메모리 객체'만 수정하면 된다.
},
wrapper: ViewModel.get({
styles: { width: '50%', background: '#fff', cursor: 'pointer' },
events: { click(e, vm) { vm.isStop = true } }
}),
title: ViewModel.get({
properties: { innerHTML: 'Title' }
}),
contents: ViewModel.get({
properties: { innerHTML: 'Contents' }
})
})
const f = () => {
viewmodel2.changeContents()
binder.render(viewmodel2)
if (!viewmodel2.isStop) requestAnimationFrame(f)
}
requestAnimationFrame(f)
</script>
</body>
</html>
簡単に言えば、必要なViewModelを変更するだけで、スタイル、イベント、プロパティに関連するコンテンツを理解して追加し、プロセッサを登録してスタイルを適用することができます.(Binderでこれらのキャラクタを処理する制御反転が発生)整理する
new Scanner
スキャナインスタンスの作成data-viewmodel
プロパティを検証し、戻ってきたconst binderreturn new ViewModel()
でインスタンスを作成binder.render
で適用します.Binderは#itemsで構成され、その配列内容はそれぞれBinderItemを含む.(最初のBinderItemの内容はBinderの0番目の配列BinderItemの属性の内容と同じです.)
に感銘を与える
無理に理解できそうですが、コードで書かせてもらうと、本当に難しいと思い、今はできません.=>最后にもっとやれば分かるよ.頑張りましょう.
ソース
せんじく
再整理方法
Reference
この問題について(コード断片86オブジェクト向けJavaScript-第2部第3部(ステップ31)), 我々は、より多くの情報をここで見つけました https://velog.io/@khw970421/코드스피츠-86-객체지향-자바스크립트-2회차-part3-step-31テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol