簡體   English   中英

這種模式適用於合理的小型 JS 應用程序嗎?

[英]Is this pattern okay for reasonnably small JS applications?

我想重構一些我編寫的不使用任何特定模式的 JS 單頁應用程序。 現在我已經閱讀了一些有趣的框架(redux...),但是我的公司一般並不熱衷於采用框架,這里的每個人都在使用 vanilla JS。 所以我想保持自制,盡可能簡單。 我在舊代碼中發現的最明顯的缺陷是單體風格,因此引入關注分離的基於組件的架構似乎已經是一個巨大的改進。 這是我想出的一個模型:

 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')); });
 #componentValueDisplay, #componentAverage { margin-left: 10px; border: 1px solid black; min-width: 50px; }
 <div style="display: flex"> <div id="componentFetchButton"></div> <div id="componentAbortButton"></div> <div>Result</div> <div id="componentValueDisplay"></div> <div>Average</div> <div id="componentAverage"></div> </div>

我想知道這種模式是否會在更大、更復雜的應用程序中的某個時候碰壁。 有什么建議嗎?

我公司一般不熱衷於采用框架,這里的每個人都在使用 vanilla JS

很好奇,但我熟悉你無法控制的約束,所以我會跳過整個“避免重新發明輪子”的說法。

所以,你有兩件事要做:事件(觀察者模式)和組件(復合模式,有點)。 前者將幫助您避免組件之間的直接依賴關系,而后者將幫助您封裝邏輯(並可能構建組件樹)。 隨着應用程序的增長,兩者都將為您提供良好的服務,並且假設您在應用程序超出它們並增加復雜性時對這些模式進行迭代,那么您所擁有的應該就足夠了。

根據您的示例代碼,我確實想提供兩個建議。 隨心所欲。

首先,我將修改dispatcher程序以遵循更傳統的事件發射器/觀察器 API。 也就是說,讓它按類型對事件進行分組。 這將在幾個方面改善情況:

  1. dispatcher程序只需要通知訂閱特定事件的事件處理程序。 這樣,您就不需要隨着應用程序的增長而迭代可能成百上千的組件(訂閱者)。

  2. 然后可以拆分handleEvent方法並處理特定事件,您可以跳過所有switch語句。 您還可以為多個事件重用相同的事件處理程序,例如禁用/啟用按鈕。 請注意使用this關鍵字!

  3. 現在可以命名事件,您可以跳過eventGenerator ,例如:

     const AppEvents = { api: { running: 'api.running', succees: 'api.success', failure: 'api.failure', aborted: 'api.aborted' } }; dispatcher.subscribe(AppEvents.api.running, (event) => { // do something }) // later dispatcher.notify(AppEvents.api.running, someEventData)

其次,為了提高測試的便利性,考慮將dispatcher程序作為參數提供給組件。 對於dispatcher程序來說,這似乎並不重要,但它確實可以幫助您與組件如何使用外部依賴項保持一致。 在測試的情況下,您可以在需要時更輕松地提供模擬或存根。

額外提示:避免在 CSS 中使用#id 它們的樣式更難覆蓋,而且它們的可重用性也較低。


最后,您擁有的代碼可以工作,從業務角度來看就足夠了。 但是對於作為開發人員的您來說,理解、維護和添加附加功能的難易程度是一個問題。 最重要的是,您將希望獲得同行/同事的支持,以便他們也了解他們應該如何以及為什么應該遵循這些模式。 祝你好運!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM