了解 Twitter 前端架構 學習複雜場景數據設計

前天 @顏什麼都不記得適 邀請回答了這麼一個問題,Lucas HC:store的組織是扁平化好,還是分層級樹狀的好?大型的項目store該怎麼組織?

其中有涉及到 Twitter 前端 Redux Store 設計剖析,為了控制回答篇幅,很多細節沒有 cover,這裡再進行一下講解。

心血來潮的探索

前幾天刷 Twitter,發現 Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)發布了這麼一條推文:

大體意思就是 Twitter 前端經過重構,已經完全遷移到 React+Redux+PWA 技術棧了,後端也使用了 nodeJS,實現了「前端一統天下」,lol。

聽到這個消息之後,我覺得去深挖一下 Twitter 的 Redux Store 組織架構,將會非常有意思。

這對於在複雜場景下的前端數據學習,以及 React、Redux 數據流設計十分有意義。

State 數據設計的重要性

因為,在 Redux 數據流框架的思想下,對於數據的處理和分配很大程度上由前端掌控。

前端數據如何設計,設計的功力如何,直接決定整個項目的開發進度以及代碼強健性,甚至還決定著頁面的性能。

本文將剖析 Twitter 前端數據結構層次,如果你對 React 技術棧不是很了解,也不妨礙閱讀;同樣,如果你對這套技術棧有興趣的話,歡迎參看我的其他類似文章。本文主體內容翻譯自Ryan Johnson的:Dissecting Twitter』s Redux Store,筆者進行了一定程度的拓展。

準備工作

想要看 Twitter Redux Store 的前提是你需要配有 React Developer Tools (RDT),在 RDT tab 中選中應用根節點。

確保選中之後,在 console 面板中輸入:

// $r is a shortcut that references the selected element in RDT$r.store.getState();

接下來,我們就可以看到Redux數據樹,就像圖中所示:

設計分析

我建議大家花些時間對 store 下,每個不同的 state 進行展開,並加以學習。但在這篇文章中,由於篇幅所限,我會挑選並深挖:

  • entities/tweets
  • homeTimeline

兩個最主要也是最核心的 state 進行剖析。這兩個 states 包含了一條 tweet 的所有關聯數據。

而一條 tweet,就像下圖中我所發的:

一條 tweet 內容的數據信息全部存儲在 entities/tweets/entities 中,entities/tweets/entities可以理解為一個 normalized 的data table,它存儲了所有tweets推文的信息;

在這個 table 中,每一條 twee t都是一個鍵值對類型的 javascript object:

  • key 為該條 tweet 的id;
  • value 為該條 tweet 的數據,也是一個js object。

下圖中,我將第一條 tweet 展開,方便大家一探究竟:

了解了 tweet 結構,我們接下來看一下 Twitter 首頁的 timeline 設計。

直觀上,timeline state 一定包含了個人主頁所要展示的所有推文信息。通過 tweet id 和剛才介紹過的 entities/tweets/entities 中的 tweet 相匹配,並最終加以在timeline上展示。

如下圖:

每個用戶的首頁 timeline 信息可從 homeTimelines/timeline 找到。首頁 timeline 展示的順序,則按照 timeline 這個數組的順序排列。也就是說,timeline 數組 index 為 0 的條目,就是你在首頁 timeline 上看到的第一條 tweet;

重要的話再說一遍: 首頁 timeline 上的每條 tweet,都有一個唯一的 id,這個 id 和上面介紹的,存儲在entities/tweets/entities 之中的 tweet id 相匹配。

看到這裡,你也許會感嘆:

「This is pretty much normalizing state shape 101 from Dan Abramov!」

沒錯,這樣的範式也是 Redux 所推崇的,完全的扁平化設計帶來的開發體驗和性能提升是無與倫比的。

當然,你可能會問為什麼 Redux 設計哲學,包括 Twitter 都在推崇扁平化的數據結構呢?

這個問題建議參考:Redux core concepts,這裡講的非常清晰,被收錄在 Redux core concepts中,強烈建議閱讀。

如果您英語吃力,可以留言與我交流,就不再展開了。

繼續言歸正傳,我們來討論一下滾動時的非同步請求設計。

首頁 timeline 載入新 tweets 方式有兩種:

  • 上拉載入 track tweets by top
  • 下滑載入 track tweets by bottom

第一種用於拉取更新的 tweets,第二種用於拉取更舊的 tweets;比如你新發了一條 tweet,就要上拉,方可顯示在 timeline 上;如果沒有最新的,向下滑動到底部後,自動載入時間上更早的tweets。

用一個等式來表達:

top = new tweets, bottom = older tweets

這種情況下,homeTimelines 下的 lastFetch.bottom 和 lastFetch.top,分別為時間戳,記錄最後一次更新數據的信息(上拉和下滑)。

  • lastFetch.bottom: 記錄最後一次向下滑動而更新數據的信息;
  • lastFetch.top: 記錄最後一次下上拉取而更新數據的信息;

同時,

cursor.bottom 和 cursor.top 值分別為一個 tweet id,表示當前 timeline 上,最上邊和最底部分別是哪一條 tweet。

  • cursor.bottom: 記錄屏幕最底部 tweet ID;
  • cursor.top: 記錄屏幕最頂部 tweet ID;

homeTimelines 裡面還記錄了 isLoadingDirections.bottom 和 isLoadingDirections.top 來表示數據載入的觸發源頭。

如圖:

最後一個非常有意思的是,entities 下除了存在 entities/tweets 之外,還分別有 cards, lists 和 users;

  • entities/tweets
  • entities/cards
  • entities/lists
  • entities/users

他們用來表示不同的推文特性。

當你打開這其餘三項的時候,會發現這三項與 entities/tweets 保持相同的結構,他們都有一個fetchStatus 的 data table,key 為 tweet id, value 為載入狀態,據統計一共有一下幾種:

  • 『none』;
  • 『loading』;
  • 『loaded』;
  • 『failed』.

這幾種狀態的設置無外乎這麼幾個目的:

  • 保證在 loading 狀態或 loaded 的 tweet 不會再發送請求給 server;
  • 在未載入完時,可以顯示載入動畫或者展點陣圖;
  • 在載入失敗時,可以顯示失敗提示或者在此請求時進行補救。

總結

本文分析了 Twitter 在採用 Redux 架構下的數據設計結構,在一個複雜的場景下,希望引起讀者對 Redux 能有一個更深入的認識。

我的其他幾篇關於React技術棧的文章:

  • React Redux 中間件思想遇見 Web Worker 的靈感(附demo)
  • React 探秘 - React Component 和 Element(文末附彩蛋demo和源碼)
  • 從setState promise化的探討 體會React團隊設計思想
  • 通過實例,學習編寫 React 組件的「最佳實踐」
  • React 組件設計和分解思考
  • 從 React 綁定 this,看 JS 語言發展和框架設計
  • React 服務端渲染如此輕鬆 從零開始構建前後端應用
  • 做出Uber移動網頁版還不夠 極致性能打造才見真章
  • React+Redux打造「NEWS EARLY」單頁應用 一個項目理解最前沿技術棧真諦

Happy Coding!

PS: 作者 Github倉庫 和 知乎問答鏈接 歡迎各種形式交流。


推薦閱讀:

前端入門第五彈:初探前端職場
前端每周清單第15期:Node.js v8.0發布;從React遷移到 Vue;前端開發的未來
名人堂 | 張克軍(Kejun):寫給初學前端工程師的一封信
前端leader們怎樣看簡歷?
普通一本工科想轉行前端開發,要不要去培訓?

TAG:前端开发 | React | 前端工程师 |