Habr前端開發者日誌:重構與反思

Habr前端開發者日誌:重構與反思

我一直對 Habr 的內部結構、工作流程的結構、通訊的結構、使用的標準以及通常如何編寫程式碼感興趣。 幸運的是,我得到了這樣的機會,因為我最近成為了habra團隊的一員。 以行動版的小重構為例,我將嘗試回答這個問題:在前台工作感覺如何。 程序中:Node、Vue、Vuex 和 SSR 以及來自 Habr 個人經驗筆記的醬料。

關於開發團隊,你首先需要了解的是我們的人數很少。 還不夠 - 這是三個前鋒,兩個後衛和所有哈布爾 - 巴克斯利的技術領先。 當然,還有一名測試員、一名設計師、三名 Vadim、一把奇蹟掃帚、一名行銷專家和其他 Bumburum。 但哈布爾的消息來源只有六名直接貢獻者。 這是相當罕見的——一個擁有數百萬美元受眾的項目,從外面看起來像一個巨大的企業,實際上看起來更像是一個舒適的初創公司,擁有盡可能扁平的組織結構。

與許多其他 IT 公司一樣,Habr 宣揚敏捷理念、CI 實踐,僅此而已。 但根據我的感覺,Habr 作為一個產品,更多的是波浪式的發展,而不是持續性的發展。 因此,在連續的幾個衝刺中,我們勤奮地編碼一些東西,設計和重新設計,破壞一些東西並修復它,解決問題並創建新的問題,踩耙子搬起石頭砸自己的腳,以便最終將功能發佈到生產。 然後會有一段平靜期,一段重建時期,是時候做「重要但不緊急」象限中的事情了。

以下要討論的正是這個「淡季」衝刺。 這次它包括對 Habr 行動版的重構。 總的來說,該公司對它寄予厚望,未來它應該取代 Habr 化身的整個動物園,成為通用的跨平台解決方案。 有一天將會出現自適應佈局、PWA、離線模式、用戶自訂以及許多其他有趣的東西。

我們來設定任務

有一次,在一次普通的站會上,一位前台談到了行動版評論組件的架構問題。 考慮到這一點,我們以團體心理治療的形式組織了一個微型會議。 大家輪流說哪裡痛,他們把一切都記錄在紙上,他們同情,他們理解,只是沒有人鼓掌。 結果是列出了 20 個問題,這清楚地表明移動 Habr 的成功之路仍然漫長而荊棘。

我主要關心的是資源使用的效率以及所謂的流暢的介面。 每天,在回家-工作-回家的路上,我都會看到我的舊手機拼命地試圖在提要中顯示 20 個頭條新聞。 它看起來像這樣:

Habr前端開發者日誌:重構與反思重構前的移動Habr介面

這裡發生了什麼事? 簡而言之,伺服器以相同的方式向每個人提供 HTML 頁面,無論使用者是否登入。 然後客戶端 JS 被載入並再次請求必要的數據,但針對授權進行了調整。 也就是說,我們實際上做了同樣的工作兩次。 介面閃爍,用戶額外下載了一百個千位元組。 細節上,一切看起來都更加令人毛骨悚然。

Habr前端開發者日誌:重構與反思舊的 SSR-CSR 方案。 授權只能在 C3 和 C4 階段進行,此時 Node JS 不忙於產生 HTML 並且可以代理對 API 的請求。

一位 Habr 用戶非常準確地描述了我們當時的架構:

手機版太垃圾了我就是這麼說的。 SSR 和 CSR 的糟糕結合。

我們不得不承認這一點,無論多麼悲傷。

我評估了這些選項,在 Jira 中創建了一張票,並在“現在很糟糕,做正確的事情”級別上進行了描述,並大致分解了任務:

  • 重複使用數據,
  • 最小化重繪次數,
  • 消除重複請求,
  • 讓加載過程更加明顯。

讓我們重用數據

理論上,伺服器端渲染旨在解決兩個問題:不受搜尋引擎方面的限制 SPA 索引 並改進指標 FMP (不可避免地惡化 TTI)。 最後在一個經典場景中 2013 年 Airbnb 制定 今年(仍在 Backbone.js 上),SSR 是在 Node 環境中運行的同構 JS 應用程式。 伺服器只是發送生成的佈局作為對請求的回應。 然後再水合發生在客戶端,然後一切都可以正常運行,無需重新載入頁面。 對 Habr 來說,與許多其他包含文字內容的資源一樣,伺服器渲染是與搜尋引擎建立友好關係的關鍵要素。

儘管該技術問世已經過去六年多了,在這段時間裡,前端世界確實已經過了不少水,但對於許多開發者來說,這個想法仍然籠罩在秘密之中。 我們沒有袖手旁觀,將支援 SSR 的 Vue 應用程式推出到生產環境,但遺漏了一個小細節:我們沒有將初始狀態發送給客戶端。

為什麼? 這個問題沒有確切的答案。 要么他們不想增加伺服器回應的大小,要么因為一堆其他架構問題,或者它根本沒有成功。 無論如何,丟棄狀態並重複使用伺服器所做的一切似乎非常合適和有用。 這個任務其實很簡單—— 狀態只是簡單地註入 到執行上下文中,Vue 會自動將其作為全域變數新增至產生的佈局: window.__INITIAL_STATE__.

出現的問題之一是無法將循環結構轉換為 JSON (循環參考); 透過簡單地將此類結構替換為平坦的對應結構即可解決。

此外,在處理 UGC 內容時,您應該記住,資料應轉換為 HTML 實體,以免破壞 HTML。 為了這些目的,我們使用 he.

最小化重繪

從上圖中可以看出,在我們的例子中,一個 Node JS 實例執行兩項功能:SSR 和 API 中的“代理”,其中發生使用者授權。 這種情況導致JS程式碼在伺服器上執行時無法進行授權,因為節點是單執行緒的,而SSR功能是同步的。 也就是說,當呼叫堆疊正忙於某些事情時,伺服器根本無法向自身發送請求。 事實證明,我們更新了狀態,但介面並沒有停止抽搐,因為必須考慮到使用者會話來更新客戶端上的資料。 我們需要教導我們的應用程式將正確的資料置於初始狀態,同時考慮到使用者的登入。

問題的解決方案只有兩種:

  • 將授權資料附加到跨伺服器請求;
  • 將 Node JS 層拆分為兩個單獨的實例。

第一個解決方案需要在伺服器上使用全域變量,而第二個解決方案將完成任務的期限延長了至少一個月。

如何做出選擇? 哈布爾經常沿著阻力最小的路徑前進。 非正式地,人們普遍希望將從想法到原型的周期縮短到最短。 對產品的態度模式有點讓人想起 booking.com 的假設,唯一的區別是 Habr 更認真地對待使用者回饋,並相信你作為開發人員可以做出這樣的決定。

遵循這個邏輯和我自己快速解決問題的願望,我選擇了全域變數。 而且,正如經常發生的那樣,你遲早必須為此付出代價。 我們幾乎立即付款:我們在周末工作,清理了後果,寫道 驗屍 並開始將伺服器分為兩部分。 這個錯誤非常愚蠢,並且涉及它的錯誤不容易重現。 是的,這確實是一種恥辱,但不管怎樣,我的帶有全局變量的 PoC 仍然在跌跌撞撞和呻吟中投入生產,並且在等待遷移到新的“雙節點”架構時運行得相當成功。 這是重要的一步,因為正式目標已經實現——SSR 學會了提供一個完全可以使用的頁面,並且 UI 變得更加平靜。

Habr前端開發者日誌:重構與反思第一階段重構後的移動 Habr 介面

最終,行動版的SSR-CSR架構導致了這樣的畫面:

Habr前端開發者日誌:重構與反思“雙節點”SSR-CSR 電路。 Node JS API 始終為非同步 I/O 做好準備,並且不會被 SSR 函數阻塞,因為後者位於單獨的實例中。 不需要查詢鏈#3。

消除重複請求

執行操作後,頁面的初始渲染不再引發癲癇。 但在 SPA 模式下進一步使用 Habr 仍然造成了混亂。

由於用戶流的基礎是表單的轉換 文章列表 → 文章 → 評論 反之亦然,首先優化該鏈的資源消耗非常重要。

Habr前端開發者日誌:重構與反思返回帖子提要會引發新的資料請求

沒有必要深挖。 在上面的截圖影片中,您可以看到應用程式在向後滑動時重新要求文章列表,並且在請求期間我們看不到文章,這意味著之前的資料在某處消失了。 看起來文章列表元件使用本地狀態並在銷毀時丟失它。 事實上,應用程式使用了全域狀態,但 Vuex 架構是直接建構的:模組與頁面綁定,而頁面又與路由綁定。 此外,所有模組都是「一次性的」——每次後續訪問該頁面都會重寫整個模組:

ArticlesList: [
  { Article1 },
  ...
],
PageArticle: { ArticleFull1 },

總的來說,我們有一個模組 文章列表,其中包含類型的對象 文章 和模組 頁面文章,這是該物件的擴充版本 文章, 有點兒 文章全文。 總的來說,這個實現本身並沒有什麼可怕的——它非常簡單,甚至有人可能會說很天真,但非常容易理解。 如果每次更改路線時都重置模組,那麼您甚至可以忍受它。 但是,例如,在文章提要之間移動 /飼料 → /全部,保證丟​​掉與個人提要相關的所有內容,因為我們只有一個 文章列表,您需要將新資料放入其中。 這再次導致我們重複請求。

在收集了我能夠挖掘到的有關該主題的所有內容後,我制定了一個新的狀態結構並將其呈現給我的同事。 討論很長一段時間,但最終贊成的論點超過了質疑,我開始實施。

解決方案的邏輯最好透過兩個步驟來揭示。 首先我們嘗試將 Vuex 模組與頁面解耦並直接綁定到路由。 是的,商店中會有更多的數據,getter 會變得更複雜一些,但我們不會加載文章兩次。 對於行動版本來說,這也許是最有力的論點。 它看起來像這樣:

ArticlesList: {
  ROUTE_FEED: [ 
    { Article1 },
    ...
  ],
  ROUTE_ALL: [ 
    { Article2 },
    ...
  ],
}

但是,如果文章清單可以在多個路線之間重疊怎麼辦?如果我們想重複使用物件資料怎麼辦? 文章 渲染貼文頁面,將其變成 文章全文? 在這種情況下,使用這樣的結構會更符合邏輯:

ArticlesIds: {
  ROUTE_FEED: [ '1', ... ],
  ROUTE_ALL: [ '1', '2', ... ],
},
ArticlesList: {
  '1': { Article1 }, 
  '2': { Article2 },
  ...
}

文章列表 這裡它只是一種文章存儲庫。 在使用者會話期間下載的所有文章。 我們非常小心地對待它們,因為這些流量可能是在地鐵站之間的某個地方通過痛苦下載的,我們絕對不想通過強迫用戶加載他已經加載的數據來再次給用戶帶來這種痛苦。下載了。 一個東西 文章ID 只是一個指向物件的 ID 陣列(就好像「連結」) 文章。 這種結構可讓您避免重複路由共有的資料並重複使用對象 文章 透過將擴充資料合併到其中來呈現貼文頁面時。

文章列表的輸出也變得更加透明:迭代器組件迭代帶有文章 ID 的數組,並繪製文章預告組件,將 Id 作為 prop 傳遞,而子組件反過來從 文章列表。 當您前往發布頁面時,我們會從以下位置取得已經存在的日期 文章列表,我們發出請求以獲取丟失的資料並將其添加到現有物件中。

為什麼這種方法比較好? 正如我上面所寫,這種方法對於下載的資料更加溫和,並允許您重複使用它。 但除此之外,它也為一些完全適合這種架構的新可能性開闢了道路。 例如,在文章出現時進行輪詢並將其載入到提要中。 我們可以簡單地將最新的帖子放在“存儲”中 文章列表,將新 ID 的單獨清單儲存在 文章ID 並通知用戶。 當我們單擊“顯示新出版物”按鈕時,我們只需將新 ID 插入到當前文章列表數組的開頭,一切都會神奇地進行。

讓下載更愉快

重構蛋糕上的錦上添花是骨架的概念,它使得在緩慢的網路上下載內容的過程不再那麼令人厭惡。 沒有對這個問題進行討論;從想法到原型的過程實際上花了兩個小時。 設計實際上是自行繪製的,我們教導我們的元件在等待資料時渲染簡單、幾乎不閃爍的 div 區塊。 主觀上,這種負荷方法實際上減少了​​使用者體內壓力荷爾蒙的數量。 骨架看起來像這樣:

Habr前端開發者日誌:重構與反思
哈布拉裝載

反思

我在哈布雷工作了六個月,我的朋友們仍然會問:嗯,你喜歡那裡嗎? 好吧,舒服——是的。 但有一些東西使這項工作與其他工作不同。 我所在的團隊對他們的產品完全漠不關心,不知道也不了解他們的用戶是誰。 但這裡一切都不同了。 在這裡,你感到對自己所做的事情負責。 在開發功能的過程中,您部分成為該功能的所有者,參加與您的功能相關的所有產品會議,自己提出建議並做出決策。 製作一個你自己每天使用的產品非常酷,但是為那些可能比你更擅長的人編寫程式碼只是一種令人難以置信的感覺(沒有諷刺)。

所有這些更改發布後,我們收到了積極的回饋,這非常非常好。 這很鼓舞人心。 謝謝你! 多寫點。

讓我提醒您,在全域變數之後,我們決定更改架構並將代理層分配到單獨的實例中。 「雙節點」架構已經以公測的形式發布。 現在任何人都可以切換到它並幫助我們使移動 Habr 變得更好。 這就是今天的全部內容。 我很樂意回答您在評論中提出的所有問題。

來源: www.habr.com

添加評論