Relay 應用程式編寫方法實現了最佳執行效能和應用程式可維護性的獨特組合。在這篇文章中,我將描述大多數應用程式在資料提取方面被迫做出的權衡,然後描述 Relay 的方法如何讓您避開這些權衡,並在多個權衡維度上實現最佳結果。
在 React 等基於元件的 UI 系統中,一個重要的決策是在 UI 樹中的哪個位置提取資料。雖然可以在 UI 樹中的任何位置完成資料提取,但為了了解其中的權衡,讓我們考慮兩個極端
- 葉節點:直接在每個使用資料的元件內提取資料
- 根節點:在 UI 根部提取所有資料,並使用 prop 傳遞將其傳遞到葉節點
您在 UI 樹中的哪個位置提取資料會影響應用程式的效能和可維護性的多個維度。不幸的是,使用簡單的資料提取方法,對於所有維度而言,這兩個極端都不是最佳的。讓我們看看這些維度,並考慮當您將資料提取移近葉子時,哪些維度會改善,而當您將資料提取移近根部時,哪些維度會改善。
載入體驗
- 🚫 葉節點:如果個別節點提取資料,您最終會得到請求串聯,其中您的 UI 需要串行(瀑布式)發出多個請求往返,因為 UI 的每一層都被其父層的渲染所阻擋。此外,如果多個元件恰好使用相同的資料,您最終會多次提取相同的資料
- ✅ 根節點:如果您在根部提取所有資料,您將發出單一請求並渲染整個 UI,而不會有任何重複的資料或串聯請求
Suspense 串聯
- 🚫 葉節點:如果每個個別元件需要單獨提取資料,每個元件都會在初始渲染時暫停。使用 React 的當前實作,取消暫停會導致從最近的父級 Suspense 邊界重新渲染。這表示您需要在初始載入期間重新評估產品元件程式碼 O(n) 次,其中 n 是樹的深度。
- ✅ 根節點:如果您在根部提取所有資料,您將暫停一次並僅評估產品元件程式碼一次。
可組合性
- ✅ 葉節點:在新位置中使用現有元件就像渲染它一樣簡單。移除元件就像不渲染它一樣簡單。同樣地,新增/移除資料相依性也可以在完全局部完成。
- 🚫 根節點:將現有元件新增為另一個元件的子元件需要更新每個包含該元件的查詢,以提取新資料,然後將新資料透過所有中間層傳遞。同樣地,移除元件需要將這些資料相依性追溯到每個根元件,並確定您移除的元件是否是該資料的最後一個剩餘消費者。相同的動態適用於將新資料新增/移除到現有元件。
細微更新
- ✅ 葉節點:當資料變更時,每個讀取該資料的元件都可以個別重新渲染,避免重新渲染不受影響的元件。
- 🚫 根節點:由於所有資料都源自根部,當任何資料更新時,它總是會強制根元件更新,從而強制重新渲染整個元件樹,這是很耗費資源的。
Relay
Relay 利用 GraphQL 片段和編譯器建置步驟來提供更佳的替代方案。在使用 Relay 的應用程式中,每個元件都會定義一個 GraphQL 片段,宣告它需要的資料。這包括元件將渲染的具體值,以及它將渲染的每個直接子元件的片段(依名稱引用)。
在建置時,Relay 編譯器會收集這些片段,並為應用程式中的每個根節點建置單一查詢。讓我們看看此方法如何應用於上面描述的每個維度
- ✅ 載入體驗 - 編譯器產生的查詢在單一往返中提取表面所需的所有資料
- ✅ Suspense 串聯 - 由於所有資料都在單一請求中提取,我們只會暫停一次,而且就在樹的根部
- ✅ 可組合性 - 從元件新增/移除資料,包括渲染子元件所需的片段資料,可以在單一元件內局部完成。編譯器會負責更新所有受影響的根查詢
- ✅ 細微更新 - 因為每個元件都會定義一個片段,Relay 確切地知道每個元件使用了哪些資料。這讓 Relay 可以執行最佳更新,在資料變更時,只會重新渲染最少數量的元件
總結
如您所見,Relay 使用宣告式、可組合的資料提取語言 (GraphQL),結合編譯器步驟,讓我們能夠在上述所有權衡維度中實現最佳結果
葉節點 | 根節點 | GraphQL/Relay | |
---|---|---|---|
載入體驗 | 🚫 | ✅ | ✅ |
Suspense 串聯 | 🚫 | ✅ | ✅ |
可組合性 | ✅ | 🚫 | ✅ |
細微更新 | ✅ | 🚫 | ✅ |