- 原文地址:Building React Components for Multiple Brands and Applications
- 原文作者:Alex Grigoryan
- 譯文出自:掘金翻譯計劃
- 譯者:XatMassacrE
- 校對者:Tina92、reid3290
為多個品牌和應用構建 React 元件
沃爾瑪大家庭由多個不同的品牌組成,其中包括 Sam’s Club, Asda,和例如 Walmart Canada 之類的地區分支。電商應用通常會使用大量類似的功能,例如信用卡元件、登入表單、新手引導、輪播圖、導航欄等等。然而為每一個獨立的品牌開發他們的電商應用將會降低程式碼的複用率,這將導致在相似功能的元件上耗費大量的時間進行重複性的工作。在 @WalmartLabs , 程式碼的複用性對我們非常重要。這就是為什麼我們的產品架構是基於多租戶或者說多重品牌來構建的 —— 其實就是在為一個品牌構建元件的同時把這些元件應用在其他擁有不同外觀和內容的品牌上的一種行為。接下來,你將會看到我們的React元件的多重品牌策略。
就像上面說的,我們的大部分服務都是建立在不同型別的多租戶上的。當你訪問服務的時候,通常情況下你會在標頭或者有效載荷上傳遞租戶,然後該服務會給特定的租戶提供資料。舉例來說對於 samsclub.com 和 walmart.com,服務會拉取不同的專案資料。
然後我們就嘗試著在前端應用上推廣這個想法。因為我們使用 React 和 Redux,檢視層元件已經和應用的 state,actions 以及 reducers 分離開了。這意味著我們可以將 React 元件抽象出來作為一個 GitHub 組織,將 Redux actions,reducers 和已連線的元件抽象成另一個。通過把這些釋出在 npm 的私人地址上,我們的開發者就可以輕易地安裝,除錯和升級這些分享出來的 UI 介面以及實現了我們業務邏輯的 actions 和 reducers 以及 API 呼叫。 你可以瞭解更多關於我們這個地方的複用。
當然,如果這就結束了,那麼我們所有應用的外觀和行為都將會是一模一樣的了。然而實際上,每一個品牌對於視覺指導方案,業務需求或者內容都有不同的要求,而且這些要求對於每個品牌來說都是必不可少的。
視覺差異
單純的視覺差異可以通過樣式來處理。我們的樣式主要是在元件級別。我們有一個 “style” 資料夾,在這個資料夾裡面是一些租戶資料夾,租戶資料夾裡面是租戶的特定的樣式檔案。
就像這樣:
Component
- src
- styles
- walmart
- samsclub
- grocery複製程式碼
當在元件層管理這些樣式檔案的時候,會發生一個問題,這個問題就是你的元件的 css 會相互衝突。在命名方面我是尤其沒有創造性的,所以對於我來說絕對會產生衝突。我們將會使用 CSS modules (它有一個絕妙的 logo),它會幫助我們移除意外衝突的問題(在我們的原型中已經支援了)。
在圖示方面,我們可以抽取一些常用的圖示放到一個單獨 GitHub 組織並且按照需要匯入到元件中。
這些特定租戶的 CSS 檔案和圖示在 build 的時候會使用 Webpack 打包到一起。
內容差異
基於服務地區的不同,不同的品牌有不同的內容需求。一個超級簡單的例子就是,walmart.com 和 walmart.ca 顯示 “加入購物車” 的地方,asda.com 只顯示 “加入”,而我們的 George clothing 品牌顯示 “加入籃子”,grocery.walmart.com 會顯示一個圖示。
我們使用 React-Intl 進行繁雜的內容管理。這些內容是在元件層面被管理的,和樣式類似,每個租戶都有他們自己的內容檔案。你將會在你的租戶或者品牌特定的內容資料夾(就像 CSS 一樣)裡指定你的內容,但是對於內容來講不一樣的地方是,對於沒有指定的地方我們會使用 walmart.com 預設的內容。在元件的構建過程中,基於你的租戶的構建引數,我們的 webpack 將會僅僅保留你的租戶的內容加上那些來自 walmart.com 的預設內容。
更大的差異
在租戶之間還有更大的差異,例如對於可分享元件中的 DOM 的變動我們會採取兩個策略。對於微小的 DOM 變動, 我們通過元件的屬性決定是否啟用和操作它的子元件。我們的登入表單就是這樣做的,Sam’s Club 希望在密碼錶單中有一個 “顯示密碼” 的按鈕而 Walmart 則不需要。我將會使用一個叫做 “displayShowPassword” 的屬性來管理這個租戶的特定需求。
有一點需要注意的是,如果你過份地依賴屬性來管理不同的租戶的需求的話,你的元件將會變的臃腫,這和更大的檔案佔用一樣會使得開發更加難以管理。這個問題在租戶之間的檔案路徑相互衝突時將會尤其明顯。我們正在想辦法解決這個問題。
對於更大的改變說來,我們使用高階元件與合成元件。當然,這就需要在還沒開發的時候就高瞻遠矚,在開發的第一天就思考如何構建出一個可配置的的共享元件。從長期來看,複用性的回報是值得我們額外的預先思考的。
較大差異的例子
我們使用兩個不同租戶的 “登入案例” 來說明。請看下圖,左邊的圖片需要郵箱,密碼,顯示忘記密碼的連結和一個登入按鈕,右邊則是郵箱,密碼,登入按鈕和頁首以及一些額外的連結。我們可以明顯的看到這兩個租戶的一些 UI 元素是可以共用的(舉例來說就是他們都需要郵箱地址,密碼和使用者登入),而另外一些特定的功能又是不同的(舉例來說就是右邊的租戶需要額外的連結和頁首)。
現在,在我們深入之前我想先來解釋一個問題 “對於這些看起來並不相同的 UI,我們為什麼不重新做一個而是儘可能的讓它們適用於多個品牌呢?”,從長期來講(短期也是同理)即使這些元件看起來並不相同,但是基於一個已經存在的元件做擴充所花費的努力仍然要小於重新做一個。拿登入來說,因為你需要特殊的安全和隱私需求所以你必須要注意很多地方例如離開站點後哪些是不可見的,然後還要保證你擁有自動資料採集許可,而且還要支援所有的瀏覽器和移動端,處理錯誤,編寫表單的自動填充(記住,我們還共享了 redux )。在元件初始化的時候除了這個盒子以外的所有東西都需要被複制一遍。在未來還有可能發生例如 samsclub 需要優化想要 “顯示密碼” 或者 walmart 想要一個註冊區域的需求。從本質上講,只要一個團隊修復了 bug ,做了 a/b 測試或者改進了表單,那麼這些新增的部分都會被分享到所有的租戶和品牌。
好了,對於一直闡述為什麼這個問題我感到很抱歉,接下來就讓我們來討論下如何解決在共享程式碼的同時又能夠提供個性化和擴充性的問題吧。
下面,我們將會應用之前討論的兩點 —— 使用組合和屬性來控制一個元件的特性。
我們將會使用一個不同的例子來從面向切面程式設計的角度來解決問題。面向切面程式設計(AOP)是旨在通過允許分離問題的切面來增加模組性的一個程式設計正規化。在這個例子中我們將會試著對 React 元件做一個橫切面概念的 “追蹤分析” 。那麼如何來解決這個問題呢?
我們將會使用上面提到的 “高階元件” 的概念。
如果租戶們在做追蹤的時候有不同的方法,那麼我們將對每個特定的租戶使用不同的 HOC。
在上述策略中,我們要確保編寫的元件是遵循像單一職責原則,避免重複原則 之類的可以輔助不同租戶間的程式碼共享的基本軟體開發原則。
這些就是我們在 @WalmartLabs 基於多租戶策略的基礎元素。同時也是我們能夠開發出健壯,可維護的並且在不犧牲本地化和品牌化的前提下共享一個通用後端的應用的至關重要的基石。