使用React構建大型應用的最佳實踐

oschina發表於2016-05-30

Sift Science在產品中使用React已經有差不多一年的時間。這段時間裡,我們的應用從Backbone + React frankenstein的hybrid app擴充為包含一個相當龐大的React元件體系。我們需要儘可能平滑的擴充套件我們的UI程式碼,本文將介紹其中所用到的技術和最佳實踐。期間也會涉及到一些一些通用的元件設計模式

希望本文能對你維護能夠一個自我構建(而不是侵入式的)的React程式碼庫有所幫助,能在UI複雜度增長的過程中幫你節省時間,保持條理清晰,此外還會為你提供一些相關工具。

在componentDidUpdate中做更多的事

React就是將不能避免更新任務的DOM轉換成聲名式的DOM。它以聲名的方式將不可避名的行為轉換為props和state的函式,從而得到相同的效果。 舉個例子:

使用React構建大型應用的最佳實踐

示例中我們構建一個界而檢視和修改聯絡人. 在截圖的右邊部分, “contact #3” 包含未儲存更改. 我們希望表單可以自動儲存一旦使用者瀏覽下一個聯絡人,在這裡是 Contact #2.

一個聰明的方法就是實現一個方法在我們的主元件中,好像這樣:

navigateToContact: function(newContactId) {
  if (this._hasUnsavedChanges()) {
    this._saveChanges();
  }

  this.setState({
    currentContactId: newContactId
  });
}
…
navigateToContact(‘contact2’)

這是一個比較稚嫩的實現. 我們需要確保“contact #2”側邊欄選單項和左下方的“< prev contact”點選事件的處理方法都連結到navigateToContact函式,而不是直接設定currentContactId的狀態。

如果我們在componentDidUpdate中使用宣告式的實現,就會是這樣。

componentDidUpdate: function(prevProps, prevState) {
  if (prevState.currentContactId !== state.currentContactId) {
    if (this._hasUnsavedChanges()) {
      this._saveChanges();
    }
  }
}

在這個版本中,在瀏覽新聯絡人同時儲存上一個聯絡人的功能實現在元件的生命週期中。由於所有的事件處理函式都能直接呼叫this.setState({currentContactId:’contact2′})而不是具體使某個特定的方法,現在這樣更難於打斷。

確實這個例子極其簡單。在所有事件處理方法都呼叫navigateToContact也不算太差,但元件越來越複雜時,問題就會顯現。宣告式的行為基於prop和state的改變而執行相應處理,會使你的元件更多的自主性和更可靠。對於需要管理許多state的元件來說這個技術尤其有用,而且令重構對於我們更加的可操作。

儘可能多的使用元件

構建一個強壯的、可維護的、和組合性好的元件庫會使你的控制器元件更容易構建。在Thinking in React 教程中,作者建議把單一責任原則作為劃分元件的依據。

我們的程式碼庫的可滑動元件就是以上思想的一個例子。一個可滑動元件能且只能處理下屬元件的滑入滑出動畫。雖然這可能看起來像是我們在濫用單一責任原則,但實際上,可滑動元件為我們節省了不少時間,因為它能很好地處理滑動動畫。可以從任何方向滑動元件,並使用邊緣作為錨。只要上級元件需要,它就可以使用JS替代預設的CSS 來實現變換。而且該元件相容多種瀏覽器,並經過了單元測試。(因此要實施的東西也比單純用css transition group更多)。有了這種積木,我們就不用太擔心像摺疊皮膚或者通知中心,我們的角標通知系統這些元件的動畫細節。

合理劃分出重用性更高的元件使我們的團隊更加高效,保證了一致的外觀和美感,並降低了非前端團隊成員向UI添磚加瓦的壁壘。下面的幾節包含構建元件時保持組合性的技巧。

狀態歸屬問題

往上頂

React 的文件中,還有另外一個必讀章節。該章節建議我們儘可能保持元件的無狀態特性。如果你發現自己在子父元件中複製或者同步狀態,就把這個狀態完全移出子元件。讓父元件管理狀態並向其子元件傳遞prop。

就拿選擇器元件,一個定製的HTML<select>標籤,說事。“當前選中的選項”應該放哪兒?通常一個選擇器元件用來展示某些外部資料,比如說一個資料模型的特定欄位的值。如果我們在選擇器內部建立一個叫“選中選項”的狀態,那麼當使用者選擇一個新的選項時,我們就不得不同時更新模型中對應的狀態和這個“選中選項”。這種重複的狀態 可以通過讓選擇器元件向父元件接受一個“選中選項”的prop,而不是維護一個自己的狀態來避免。

直觀上,既然元件是用來展示某個資料模型的,那麼這種狀態屬於這個模型。選擇器元件只是一個幾乎沒有狀態的UI控制元件,並且資料模型是對應的後端。只所以說是幾乎是因為選擇器事實上可以包含一點兒狀態:它當前是否已經被展開。這個狀態可以讓它永久定居在選擇器內部吧,因為它只是UI部分的細節,而且通常父元件也鳥它。在下一節中,我們將展示“當前是否已經被展開”這個狀態是怎樣被傳遞到下一層的元件當中去,以遵守單一責任原則。

從邏輯層分離出UI細節

我們已經使用了有狀態的高層次元件和無狀態的較低階別的元件模式。這些無狀態的元件提供了UI渲染細節、樣式和標記的重用。有狀態的包裝元件提供了互動邏輯的重用。讓元件具有複用性的這種模式已經成為我們最重要的規則。這裡有一個細節是關於如何建立Select元件,以及如何重用UI程式碼。這段UI程式碼也應用在了ToolTipToggle元件中。

使用React構建大型應用的最佳實踐

Select元件

Select元件與HTML裡的<select>標籤很相似。它需要一個選擇列表作為當前的選項集合,但是這是沒有狀態屬性的。甚至沒有屬性表明是否為當前展開的狀態。Select元件是由具有下拉展開和摺疊狀態的DropdownToggle元件構成。

DropdownToggle

這個元件在點選觸發時,元素和子節點將會顯示一個下拉的HoverCard 。 Select (選擇)通過一個按鈕和一個向下的箭頭圖示觸發DropdownToggle。它還可以通過可選擇的選項列表作為DropdownToggle的子節點。

TooltipToggle

TooltipToggle是類似於DropdownToggle的,都是接收觸發器管理HoverCard子節點的顯示。不同之處是如何顯示HoverCard; 他們的互動邏輯是不一樣的。DropdownToggle監聽單擊觸發元素,TooltipToggle監聽滑鼠懸停事件。當ESC鍵被按下的時候,TooltipToggle是不關閉的,而DropdownToggle是會關閉的。

HoverCard

HoverCard是顯示層的明星。它增強了UI標籤,風格和工具提示,以及下拉選單的一部分事件處理。它沒有狀態,也不知道是否開啟或關閉。如果它存在意味著開啟,解除安裝了就是關上它。

HoverCard接受錨元素作為一個屬性,這是懸浮位置的定位元素。HoverCard也有多種樣式和感覺。一種樣式叫工具提示(tooltip),它有黑色背景和白色文字。另一種樣式叫下拉選單(dropdown),它被Select元件呼叫使用,白色的背景和盒子的陰影。

HoverCard也可以定製化多種屬性,例如是否顯示一個三角符號(TooltipToggle開啟了這個屬性)。

或者通過位置屬性明確HoverCard與錨點的關係(TooltipToggle 使用“top” 而 DropdownToggle 使用 “bottom”),等等這些。

HoverCard也監聽外部的某些事件(例如clicks)或者ESC按鍵,當這些事件發生了,HoverCard通過屬性回撥(prop callbacks)通知父元素,由父元素決定是否關閉HoverCard。HoverCard另一個能力是偵聽當前位置是否在視窗中溢位了,如果是,立刻修改當前位置。
(這個功能也可以通過屬性值來關閉)

正因為HoverCard提取了所有的UI細節的程式碼,所以更高層的元件(例如DropdownToggle,TooltipToggle)可以只專注於狀態管理和業務邏輯互動,而不必再實現一遍。Dom定位和樣式程式碼共享於UI的hover-y中。

這只是一個例子,分離UI細節和互動邏輯。對所有元件依據這一原則,並且仔細評估哪一部分是新狀態,肯定可以提高我們重用程式碼的能力。

什麼是 Flux?

Flux 特別適合儲存應用程式的狀態資訊,這些狀態不屬於任何的元件,並且狀態需要持久化儲存的時候。一個通常建議是不要使用this.state把什麼東西都存入Flux中——不過,這也不全對。當有些被解除安裝了,你應該使用this.state去清空不相關的特殊狀態。一個例子就是DropdownToggle元件裡的isCurrentlyOpen狀態。

Flux也很重,所以儲存本來就應該持久化到伺服器上的資料顯得很不適合。我們當前使用的是全域性Backbone模型快取,用於資料獲取和儲存。但是我們也在試驗採用REST API形式的Relay-like系統。(敬請期待更多關於這個主題)

對於所有其他的情況,我們會在程式碼中逐步介紹Flux。它很牛,因為不需要重寫。只要你想用,在哪都可以。它很容易單元測試,容易擴充套件,也提供了一些很酷的特性例如在我們的核心模型裡解決迴圈依賴問題。它也刪掉了不必要的單例模型元件。

用React代替CSS

這篇文章最後一個想分享的是:明確React元件集合是你重用程式碼的主要工具。我們的每一個React元件都關聯了一個CSS檔案。部分元件甚至沒有任何JS邏輯。他們只是標記和樣式的繫結。

我們已經遠離了類似 Bootstrap 那樣的全域性風格的類名。你肯定還是可以使用 Bootstrap 的,而且在你的 React 元件中已經包裝了Bootstrap元件,從長遠來看,這將會節省你的時間。舉例來說,有一個 Icon React 元件被封裝進內部,標記和接受圖示名稱作為支撐比不得不記住什麼標記和類名來使用圖示要好,也變得更簡單。也使得可以將功能新增進元件。

儘管我們定義了一些全域性風格的元素,例如錨(anchor)和標題(heading),以及許多全域性的 SCSS 變數,但是我們沒有真正定義全域性的 css 類。我們小心地重用 UI ,儘可能地使用 React 元件,這讓我們作為一個團隊更有效率,因為程式碼更一致和更可預測。

這是關於它的!這些都是一些指導原則,它幫助我們建立一個健壯的React架構,這擴充套件了我們的工程團隊和應用的複雜性。如果你有與這篇文章相關的想法和經驗,請發表評論。

如果你想看到更多,請檢視React提示和最佳實踐

相關文章