前幾天刷Twitter,發現Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)釋出了這麼一條推文:
大體意思就是Twitter前端經過重構,已經完全遷移到React+Redux+PWA技術棧了,後端也使用了nodeJS,實現了“前端一統天下”,lol。
聽到這個訊息之後,我覺得去深挖一下Twitter的Redux store組織架構,將會非常有意思。
這對於在複雜場景下的前端資料學習,以及React、Redux資料流設計十分有意義。
因為,在Redux資料流框架的思想下,對於資料的處理和分配完全由前端掌握。
前端資料如何設計,設計的功力如何直接完全決定整個專案的開發進度以及程式碼強健性,甚至還決定著頁面的效能。
本文將剖析Twitter前端資料結構層次,如果你對React技術棧不是很瞭解,也不妨礙閱讀;同樣,如果你對這套技術棧有興趣的話,歡迎參看我的其他類似文章:
- React Conf 2017 乾貨總結1: React + ES next = ♥
- React+Redux打造“NEWS EARLY”單頁應用 一個專案理解最前沿技術棧真諦
- 一個react+redux工程例項
- ......
歡迎關注我的主頁,更多技術文章不再錯過。
本文主體內容翻譯自Ryan Johnson的文章:Dissecting Twitter’s Redux Store,筆者進行了一定程度的擴充。
準備工作
想要看Redux store的前提是你需要配有React Developer Tools (RDT),在RDT tab中選中應用根節點。
確保選中之後,在console皮膚中輸入:
// $r is a shortcut that references the selected element in RDT
$r.store.getState();複製程式碼
接下來,我們就可以看到Redux資料樹,就像圖中所示:
設計分析
我建議大家花些時間對每個不同的state進行展開,並加以學習。但在這篇文章中,由於篇幅所限,我會挑選並深挖:
- entities/tweets和
- homeTimeline
兩個最主要也是最核心的state進行剖析。這兩個states包含了一條tweet的所有關聯資料。
一條tweet,就像下圖中我所發的:
一條tweet內容的資料資訊全部儲存在entities/tweets/entities中,entities/tweets/entities可以理解為一個normalized的data table,它儲存了所有tweets推文的資訊;
在這個table中,每一條tweet都是一個鍵值對型別的js object:key為該條tweet的id,value為該條tweet的資料,也是一個js object。
下圖中,我將第一條tweet展開,方便大家一探究竟:
瞭解了tweet儲存結構,我們接下來看一下Twitter首頁的timeline結構。
直觀上,timeline一定包含了個人主頁展示推文的資訊。通過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,
and
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 and 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能有一個更深入的認識。
本文主體內容翻譯自Ryan Johnson的文章:Dissecting Twitter’s Redux Store,筆者進行了一定程度的擴充。
Happy coding!
PS: 作者Github倉庫,歡迎通過程式碼各種形式交流。