前幾天在開源圈發生個小插曲。起因是有個用戶表示:React新文檔在文檔結構、美觀度、性能等各方面都達到很高的標準。

尤雨溪對Vue新文檔與React Beta文檔做了測試後表示:在性能這塊,Vue新文檔更具優勢。

Dan表示:當前文檔還處於Beta版本,現在有更重要的工作要完成,正式版上線前會優化性能。
話雖這麼說,Dan應該是通了個宵優化了一把性能:

本篇文章我們來看看,Dan都做了哪些優化。
優化效果經過優化後的lightHouse跑分:

作為對照,Vue文檔的跑分:

兩者10次TTI跑分對比:

這裡的TTI[1](Time to Interactive,即可交互時間),衡量的是「頁面變得完全可交互所需時間」,其中「完全可交互」指:
頁面展示了「有用信息」(由FCP衡量,FCP指First Contentful Paint)
可見頁面中大部分元素完成事件綁定,交互響應的延遲在50ms內
優化主要有兩個思路:
編譯時:減少打包體積
運行時:「非首屏必需」代碼延遲加載
之前入口處全量引入了一個工具函數utils,現在將其中方法拆分成不同文件,這樣能減少首屏bundle體積:

再比如:

這部分優化讓bundle體積減少約一半:

其次,當前Next.js(文檔使用的框架)沒有默認開啟「針對現代瀏覽器編譯」。這意味着bundle中會引入更多polyfill,有更多語法轉換及幫助函數。
Dan通過配置開啟了這個功能:

運行時優化的方式主要是:懶加載非必需資源。
新文檔中存在很多codesandbox(在線示例),這些示例依賴CodeMirror linter,但這不是首屏必需的。
所以Dan將這部分資源懶加載:

除此之外,如果你細心觀察會發現,Total Blocking Time指標下降很多:

TBT[2](Total Blocking Time,即總阻塞時間)測量頁面「被阻止響應用戶輸入(例如鼠標點擊、屏幕點擊或按下鍵盤)的總時間」(糾正:應該是FCP與TTI之間時間)
一般來說,如果JS執行時間過長,就會影響這個指標。
我們知道,頁面加載後前端框架會有首屏渲染的初始化過程。即使是服務端渲染,也會有Hydrate(注水)的過程。
而React18的Selective Hydration為解決這一問題提供了好方法。
如果你的React18應用是SSR,那麼被<Suspense/>包裹的組件部分不會參與首次Hydrate的過程。
也就是說,被<Suspense/>包裹的部分不會影響阻塞時間。
所以,雖然這部分工作很重要,但Dan需要做的,僅僅是把一些「對首屏顯示不太重要的組件」包裹在<Suspense/>中。
可以看到,在將一些組件用<Suspense/>包裹前Hydrate作為一個長任務的耗時:

當包裹之後,這個長任務持續時間顯著降低,進而降低TBT:

這些只是初步的優化結果,後續還有很多優化工作值得去做。比如INP[3](Interaction to Next Paint,與下一次Paint的交互)指標還是偏高:

Dan坦言:指標偏高的原因可能是因為 —— React本身比較慢。
他對此提出了一些猜想,你可以到這裡參與討論[4]。
參考資料TTI: https://web.dev/tti/
[2]TBT: https://web.dev/tbt/
[3]INP: https://web.dev/inp/
[4]參與討論: https://github.com/reactjs/reactjs.org/issues/4691