天天看點

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

業務背景

目前團隊内的開發模式多是面向元件的,UI層和邏輯層均強耦合在一起,由于業務的差異性,往往很難完全複用。

  • 閑魚前端業務處在高速發展不斷嘗試的階段,如何能更快更穩定地完成需求,更好的支撐業務發展絕對是一個值得探索的問題。
  • 在接手一個複雜的老業務代碼時,經過較多人的修改,往往可維護性較差,有時隻想修改某個小地方卻需要較大的了解成本,是以用一套統一的元件開發規範在長期維護中顯得格外重要。
  • 閑魚技術體系經曆了從weex、rax0.x到現在rax1.x的變更,中間有過一些前端資産的積累,但是由于遷移的成本後期都不再維護,如何用更小的成本讓業務層平穩過渡到新的技術體系?

對于以上的問題我們希望能用架構一并解決,對于該架構的目标主要包括:

  • 提高代碼可複用性
  • 規範代碼,降低長期維護成本
  • 降低業務層與技術體系的關聯

思路

關于提效,其中比較重要的是相同的代碼不要重複寫,做更細的區分和提取,提高可複用的顆粒度。另一方面是解決現有開發下比較影響開發效率的問題。

元件的分層

是以我們将面向元件的開發模式分為UI層View和邏輯層Store,以Interface進行隔離和耦合。

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖一:元件構成

在UI層無需關心狀态的流轉,隻負責展示和互動方法的調用,DOM相關的動畫互動等行為邏輯也會放到該層中。

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖二:元件分工

在确認了分層的邏輯後自然就引入了Interface,主要分為兩部分:一部分是IProps,申明該元件所需的Props,在使用者調用該元件時進行對應的提示和限制;另一部分則負責連接配接Store和View,其中包括狀态state和互動方法;見下面的Interface示例:

export interface IMultiScrollerProps {
  tabs: string[];
  onTabChange?(i: number): void;
}

export interface IMultiScroller extends IBase {
  readonly tabIndex: number;
  readonly tabSource: ITabItem[];
  readonly children: any[];

  onSwiperChange(i: number): void;
}           

總結一下:所有的state和互動方法都在store中管理,供View消費;View中隻負責和dom相關的邏輯操作,View和store的職責分界線就是View和store分别單獨使用時其互動和效果都能保持不變;以此實作View和store分别能有更多的複用。

狀态管理

現有的業務開發中基本所有的需求都是基于hooks的狀态管理,主要存在以下問題:

  • 對于較複雜的元件hooks在多次疊代後的維護成本會非常高;
    • 有時候,你的useEffect依賴某個函數的不可變性,這個函數的不可變性又依賴于另一個函數的不可變性,這樣便形成了一條依賴鍊。一旦這條依賴鍊的某個節點意外地被改變了,那麼useEffect就被意外地觸發了後面的情況就會變得不可控。
  • 異步陷阱
    • 狀态的修改是異步的

useState傳回的修改函數是異步的,并不會直接生效,是以此時讀取該值擷取到的是舊值。要在下次重繪才能擷取新值。不要試圖在更改狀态之後立馬擷取狀态。

const [value, setValue] = useState(0);
setValue(100);
console.log(value); // <- 0           
  • timeout指向的是舊值

timeout指向的是舊值,即使在外部已經重新設定,由于閉包所有在setTimeout中擷取到的都是之前的值。

const [value, setValue] = useState(0);
window.setTimeout(() => {
  console.log('setAnotherValue', value) // <- 0
}, 1000);
setValue(100);           
  • 何時使用useCallback/useMemo等對于新手來說存在一定的門檻。

關于 Hook 中的閉包:

useEffect、useMemo、useCallback都是自帶閉包的。也就是說,每一次元件的渲染,其都會捕獲目前元件函數上下文中的狀态(state、props)。是以每一次這三種 Hook 的執行,反映的也都是當時的狀态,無法擷取最新的狀态。對于這種情況,應該使用 ref 來通路。

對于狀态管理react體系中最受歡迎的應是redux與mobx

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖三:Redux Flow

redux的特點從上圖可以總結得到下面的三大原則:

  • 單一資料源
  • state 是隻讀的
  • 使用純函數來執行修改

但是redux的問題也是十分明顯的:開發者需要寫更多附加的樣闆代碼,并且留下更多需要我們維護的代碼。

與 Redux 相似的,另一個狀态管理方案是 MobX:

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖四:Mobx Flow

相比 Redux 的強規則約定,MobX 更簡單靈活,核心原理是通過 action 觸發 state 的變化,進而觸發 state 的衍生對象(Computed value & Reactions)。開發者隻需要定義需要 Observe 的資料和由此衍生的資料(Computed value)或者操作 (Reactions),剩下的更新就交給 MobX 去做就可以了。一句話總結就是:

任何源自應用狀态的東西都應該自動地獲得。

分析閑魚的業務特色并不存在5個以上同學同時維護一個項目的超大型需求,強約定的redux對我們來說收益有限,而MobX 确實比 Redux 上手更容易些,并且不需要寫很多樣闆代碼,可以提供更高效的選擇。

實作

我們給架構取名:Linke,來自switch的遊戲塞爾達,希望它能像林克一樣點亮一個個神廟。

基于上面的分析思路結合實際業務中的技術體系(Rax)最後我們設計了下面的研發體系:

UI部分也就是View還是沿用原有的Rax,UI用到的狀态也直接在View中管理。

業務邏輯部分也就是Store用Mobx的能力解決上面提到的現有hooks開發遇到的問題,兩者沒有強關聯。

Linke做為中間耦合層對他們進行限制和橋接。

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖五:基于Linke的研發體系

API

為保證開發者最低的學習成本,Linke在設計時盡可能地減少API,最終隻有一個方法和4個Store内置方法,詳見:

observer(baseComponent, Store)

保證元件能響應store中的可觀察對象(observable)變更,即store更新,元件視圖響應式更新

Store内置方法

  • 成員方法 - $$set: 所有狀态變化必須通過$$set來完成,與微信的setData()類似
  • 成員方法 - $$setProps:處理外部傳入的元件props,View初始化或者props發生變化時調用
  • 成員方法 - $$didMount:提供View的生命周期,View被插入DOM時調用
  • 成員方法 - $$unMount:提供View的生命周期,View被移除DOM時調用

可以看出Store内置方法中除了$$set其他三個都是生命周期方法,其調用順序為:$$setProps -> $$didMount -> $$unMount

demo

Interface.ts

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

index.tsx

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

store.ts

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

上面就是一個完整的元件demo。

對比

現在的元件開發子產品模式如下圖六所示,以元件為機關所有的邏輯是耦合在一起的,互相之間沒有分界,即便是相同的樣式也很難實作複用。

無論是在代碼了解還是二次開發上都存在較大的成本和不穩定性風險。

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖六:原元件的開發模式

基于Linke的元件開發模式如下圖所示:

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖七:基于Linke的元件開發模式

View和Store相對獨立沒有強耦合性,這樣的好處顯而易見:

• 通過閱讀Interface就能知道Store/View的基本邏輯,減少了解成本

• 資料邏輯和View邏輯分别在Store和View中管理,真正實作各司其職,減少維護成本。

• 最重要的一點是通過分離讓Store和View分别實作了複用,組合不同的Store/View生成不同的元件

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖八:Store分别和不同的View組合

低成本可複用前端架構——Linke業務背景思路實作對比應用下一步

圖九:不同的Store和同一個View組合

應用

目前Linke已經應用在了閑魚前端各個新項目中,包括2個線上項目和3個正在開發的項目收益明顯,什麼功能的代碼在什麼位置一目了然配合Interface中的注釋大大減少了接手項目的了解成本。

通用基礎元件和業務元件都在有序的抽離中,同時随着View/Store庫的不斷豐富,可以複用的物料資源增加,不同業務和同一業務不同場景中可以複用的View/Store越來越多,在一定程度上大大減少開發成本提高效率。

下一步

目前新财年除了現有的H5業務外,最大的特點是會對各個小程式做一些流量探索,比如淘系輕應用、微信小程式、支付寶輕應用等,這些應用的特點是與端内的H5業務及其相似,但是會有各自的細微差異。是以我們也在探索基于Linke對此類業務場景的提效。

繼續閱讀