了解 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們怎樣看簡歷?
※普通一本工科想轉行前端開發,要不要去培訓?