我想重构一些我编写的不使用任何特定模式的 JS 单页应用程序。现在我读到了一些有趣的框架(redux ...),但我的公司一般并不热衷于采用框架,这里的每个人都在使用 vanilla JS。所以我想保持自制,尽可能简单。我在旧代码中发现的最明显的缺陷是单体风格,因此引入关注分离的基于组件的架构似乎已经是一个巨大的改进。这是我想出的一个模型:
<!DOCTYPE html>
<html lang="fr">
<head>
<script>
'use strict';
let eventGenerator = (function () {
let id = -1;
return {
generate: () => {
++id;
return id;
}
};
}) ();
let dispatcher = (function () {
let components = [];
return {
addComponent: (component) => {
components.push (component);
},
dispatch: (id, detail = null) => {
for (let c of components) {
c.handleEvent (id, detail);
}
}
};
}) ();
const EVT_FAKE_API_RUNNING = eventGenerator.generate ();
const EVT_FAKE_API_SUCCESS = eventGenerator.generate ();
const EVT_FAKE_API_FAILURE = eventGenerator.generate ();
const EVT_FAKE_API_ABORTED = eventGenerator.generate ();
class ComponentFakeAPI {
constructor (param) { // param = nb de secondes à attendre
dispatcher.addComponent (this);
this.param = param;
this.timer = null;
this.result = null;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FETCH_BUTTON_CLICKED:
this.timer = setTimeout (() => {
this.result = Math.round (Math.random () * 100);
if (this.result >= 20)
dispatcher.dispatch (EVT_FAKE_API_SUCCESS, { result: this.result });
else
dispatcher.dispatch (EVT_FAKE_API_FAILURE);
}, this.param);
dispatcher.dispatch (EVT_FAKE_API_RUNNING);
break;
case EVT_ABORT_BUTTON_CLICKED:
clearTimeout (this.timer);
dispatcher.dispatch (EVT_FAKE_API_ABORTED);
}
}
}
const EVT_FETCH_BUTTON_CLICKED = eventGenerator.generate ();
class ComponentFetchButton {
constructor (elt) {
dispatcher.addComponent (this);
elt.innerHTML = `<button>fetch</button>`;
this.elt = elt;
this.but = elt.querySelector ('button');
this.but.onclick = () => dispatcher.dispatch (EVT_FETCH_BUTTON_CLICKED);
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_RUNNING:
this.but.disabled = true;
break;
case EVT_FAKE_API_SUCCESS: case EVT_FAKE_API_FAILURE: case EVT_FAKE_API_ABORTED:
this.but.disabled = false;
break;
}
}
}
const EVT_ABORT_BUTTON_CLICKED = eventGenerator.generate ();
class AbortButton {
constructor (elt) {
dispatcher.addComponent (this);
elt.innerHTML = `<button disabled>abort</button>`;
this.elt = elt;
this.but = elt.querySelector ('button');
this.but.onclick = () => dispatcher.dispatch (EVT_ABORT_BUTTON_CLICKED);
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS: case EVT_FAKE_API_FAILURE: case EVT_FAKE_API_ABORTED:
this.but.disabled = true;
break;
case EVT_FAKE_API_RUNNING:
this.but.disabled = false;
break;
}
}
}
class ComponentValueDisplay {
constructor (elt) {
dispatcher.addComponent (this);
elt.textContent = '';
this.elt = elt;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS:
this.elt.textContent = detail.result;
break;
case EVT_FAKE_API_FAILURE:
this.elt.textContent = 'failure !';
break;
case EVT_FAKE_API_ABORTED:
this.elt.textContent = 'aborted !';
break;
case EVT_FAKE_API_RUNNING:
this.elt.textContent = '';
break;
}
}
}
class ComponentAverage {
constructor (elt) {
dispatcher.addComponent (this);
elt.textContent = '';
this.elt = elt;
this.sum = 0;
this.avg = 0;
this.n = 0;
}
handleEvent (id, detail) {
switch (id) {
case EVT_FAKE_API_SUCCESS:
++ this.n;
this.sum += detail.result;
this.elt.textContent = Math.round (this.sum / this.n);
break;
}
}
}
window.addEventListener ('load', () => {
let componentFakeAPI = new ComponentFakeAPI (2000);
let componentFetchButton = new ComponentFetchButton (document.querySelector ('#componentFetchButton'));
let componentAbortButton = new AbortButton (document.querySelector ('#componentAbortButton'));
let componentValueDisplay = new ComponentValueDisplay (document.querySelector ('#componentValueDisplay'));
let componentAverage = new ComponentAverage (document.querySelector ('#componentAverage'));
});
</script>
<style>
#componentValueDisplay, #componentAverage {
margin-left: 10px;
border: 1px solid black;
min-width: 50px;
}
</style>
</head>
<body>
<div style="display: flex">
<div id="componentFetchButton"></div>
<div id="componentAbortButton"></div>
<div id="componentValueDisplay"></div>
<div id="componentAverage"></div>
</div>
</body>
</html>
我想知道这种模式是否会在更大、更复杂的应用程序中的某个时候碰壁。有什么建议吗?