新聞中心
前言
本文是 Rendering on the Web: Performance Implications of Application Architecture (Google I/O ’19) [1] 這篇谷歌工程師帶來的現(xiàn)代應(yīng)用架構(gòu)體系下的優(yōu)化相關(guān)演講的總結(jié),演講介紹了以下優(yōu)化手段:

創(chuàng)新互聯(lián)長期為超過千家客戶提供的網(wǎng)站建設(shè)服務(wù),團隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為謝通門企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、網(wǎng)站設(shè)計,謝通門網(wǎng)站改版等技術(shù)服務(wù)。擁有10余年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
預(yù)渲染
同構(gòu)渲染
流式渲染
漸進式注水(非常精彩)
應(yīng)用架構(gòu)體系
當(dāng)我們討論「應(yīng)用架構(gòu)」的時候,可以理解為通過以下幾個部分組合來構(gòu)建網(wǎng)站。
Component model組件模型。Rendering and loading渲染和加載。Routing and transitions路由和過渡。Data/state management數(shù)據(jù)、狀態(tài)的管理。
性能指標(biāo)
在分析頁面渲染性能之前,先了解一下幾個比較重要的指標(biāo),方便下文理解:
FP: First Paint,是 Paint Timing API 的一部分,是頁面導(dǎo)航與瀏覽器將該網(wǎng)頁的第一個像素渲染到屏幕上所用的中間時,渲染是任何與輸入網(wǎng)頁導(dǎo)航前的屏幕上的內(nèi)容不同的內(nèi)容。FCP: First Contentful Paint,首次有內(nèi)容的渲染是當(dāng)瀏覽器渲染 DOM 第一塊內(nèi)容,第一次回饋用戶頁面正在載入。TTI: Time to interactive 第一次可交互時間,此時用戶可以真正的觸發(fā) DOM 元素的事件,和頁面進行交互。FID: First Input Delay 第一輸入延遲測量用戶首次與您的站點交互時的時間(即,當(dāng)他們單擊鏈接,點擊按鈕或使用自定義的 JavaScript 驅(qū)動控件時)到瀏覽器實際能夠的時間回應(yīng)這種互動。TTFB: Time to First Byte 首字節(jié)時間,顧名思義,是指從客戶端開始和服務(wù)端交互到服務(wù)端開始向客戶端瀏覽器傳輸數(shù)據(jù)的時間(包括 DNS、socket 連接和請求響應(yīng)時間),是能夠反映服務(wù)端響應(yīng)速度的重要指標(biāo)。
如果你還不太熟悉這些指標(biāo)也沒關(guān)系,接下來的內(nèi)容中,會結(jié)合實際用例分析這些指標(biāo)。
渲染開銷 The cost of rendering
客戶端渲染 Client-side rendering
從服務(wù)端獲取 HTML、CSS、JavaScript 都是需要成本的,以一個 CSR(客戶端渲染)的網(wǎng)站為例,客戶端渲染的網(wǎng)站依賴框架庫(bundle)、應(yīng)用程序(app)來進行初始化渲染,假設(shè)它有 1MB 的 JavaScript Bundle 代碼,那么只有當(dāng)這一大段的代碼加載并執(zhí)行完成以后,用戶才能看到頁面。
它的結(jié)構(gòu)一般如下:
分析一下它的流程:
用戶輸入網(wǎng)址進入網(wǎng)站,拉取 HTML 資源。
- HTML 資源中發(fā)現(xiàn) script 標(biāo)簽加載的 bundle 再一次發(fā)起請求拉取 bundle。此時也是性能統(tǒng)計指標(biāo)中的
FP完成。
在這個階段,頁面基本上是沒什么意義的,當(dāng)然你也可以放置一些靜態(tài)的骨架屏或者加載提示,來友好的提示用戶。
- JavaScript bundle 下載并執(zhí)行完畢,此時頁面才真正渲染出有意義的內(nèi)容。對應(yīng)
FCP完成。
當(dāng)框架對 DOM 節(jié)點添加各類事件綁定后,用戶才真正可以和頁面交互,此時也對應(yīng) TTI 完成。
它的 缺點 在于,直到整個 JavaScript 依賴執(zhí)行完成之前,用戶都看不到什么有意義的內(nèi)容。
服務(wù)端同構(gòu)渲染 SSR with Hydration
基于以上客戶端渲染的缺點以及用戶對于 CSR 應(yīng)用交互更加豐富的需求,于是誕生了集 SSR 和 CSR 的 性能、SEO、數(shù)據(jù)獲取 的優(yōu)點與一身的「 同構(gòu)渲染 」,簡單點說,就是:
第一次請求,在服務(wù)端就利用框架提供的服務(wù)端渲染能力,直接原地請求數(shù)據(jù),生成包含完整內(nèi)容的 html 頁面,用戶不需要等待框架的 js 加載就可以看到內(nèi)容。
等到頁面渲染后,再利用框架提供的 Hydration(注水)能力,讓服務(wù)端返回的“干癟”的 HTML 注冊事件等等,變的豐富起來,擁有了各種事件后,就和傳統(tǒng) CSR 一樣擁有了豐富多彩的客戶端交互。
在同構(gòu)應(yīng)用中,只要 HTML 頁面返回,用戶就可以看到豐富多彩的頁面:
而 JavaScript 加載完畢后,用戶就可以和這些內(nèi)容進行交互(比如點擊放大、跳轉(zhuǎn)頁面等等……)
代碼對比
典型的 CSR React 應(yīng)用的代碼是這樣的:
而 SSR 的代碼則需要服務(wù)端的配合,
先由服務(wù)端通過 ReactDOMServer.renderToString 在服務(wù)端把組件給序列化成 html 字符串,返回給前端:
前端通過 hydrate 注水,使得功能交互變的完整:
Vue 的 SSR 也是同理:
同構(gòu)的缺陷
至此看來,難道同構(gòu)應(yīng)用就是完美的嗎?當(dāng)然不是,其實普通的同構(gòu)應(yīng)用只是提升了 FCP 也就是用戶看到內(nèi)容的速度,但是卻還是要等到框架代碼下載完成, hydrate 注水完畢等一系列過程執(zhí)行完畢以后才能真正的 可交互 。
并且對于 FID 也就是 First Input Delay 第一輸入延遲這個指標(biāo)來說,由于 SSR 快速渲染出內(nèi)容,更容易讓用戶誤以為頁面已經(jīng)是可交互狀態(tài),反而會使「用戶第一次點擊 - 瀏覽器響應(yīng)事件」 這個時間變得更久。
因此,同構(gòu)應(yīng)用很可能變成一把「雙刃劍」。
下面我們來討論一些方案。
Pre-rendering 預(yù)渲染。
對于不經(jīng)常發(fā)生變化的內(nèi)容來說,使用預(yù)渲染是一種很好的辦法,它在代碼構(gòu)建時就通過框架能力生成好靜態(tài)的 HTML 頁面,而不是像同構(gòu)應(yīng)用那樣在用戶請求頁面時再生成,這讓它可以幾乎立刻返回頁面。
當(dāng)然它也有很大的限制:
只適用于靜態(tài)頁面。
需要提前列舉出需要預(yù)渲染的 URLs。
流式渲染 Streaming
流式渲染可以讓服務(wù)端對大塊的內(nèi)容分片發(fā)送,使得客戶端不需要完整的接收到 HTML,而是接受到第一部分時就開始渲染,這大大提升了 TTFB 首字節(jié)時間。
在 React 中,可以通過 renderToNodeStream 來使用流式渲染:
漸進式注水 Progressive Hydration
我們知道 hydrate 的過程需要遍歷整顆 React 節(jié)點樹來添加事件,這在頁面很大的情況下耗費的時間一定是很長的,我們能否先只對關(guān)鍵的部分,比如視圖中可見的部分,進行「注水」,讓這部分先一步可以進行交互?
想象一下它的特點:
組件級別的漸進式注水。
服務(wù)端依舊整頁渲染。
頁面可以根據(jù)優(yōu)先級來分片“啟動”組件。
通過一張動圖來直觀的感受一下普通注水(左)和漸進式注水(右)的區(qū)別:
可以看到用戶第一次可以交互的時間大大的提前了。
光說不做假把式,我們看看用 React 完成這個功能的代碼,首先我們需要準(zhǔn)備一個組件 Hydrator 用來實現(xiàn)當(dāng)某個組件 進入視圖范圍以后 再進行注水。
首先來看看應(yīng)用的整體結(jié)構(gòu):
- let load = () => import('./stream');
- let Hydrator = ClientHydrator;
- if (typeof window === 'undefined') {
- Hydrator = ServerHydrator;
- load = () => require('./stream');
- }
- export default function App() {
- return (
- );
- }
根據(jù)客戶端和服務(wù)端的環(huán)境區(qū)分使用不同的 Hydrator ,在服務(wù)端就直接返回普通的 html 文本:
- function interopDefault(mod) {
- return (mod && mod.default) || mod;
- }
- export function ServerHydrator({ load, ...props }) {
- const Child = interopDefault(load());
- return (
- );
- }
而客戶端,則需要實現(xiàn)漸進式注水的關(guān)鍵部分:
- export class Hydrator extends React.Component {
- render() {
- return (
- ref={c => (this.root = c)}
- dangerouslySetInnerHTML={{ __html: '' }}
- suppressHydrationWarning
- />
- );
- }
- }
首先 render 部分,利用 dangerouslySetInnerHTML 來使得這部分初始化為空的 html 文本,并且由于 server 端肯定還是和往常一樣全量渲染內(nèi)容,而客戶端由于初始化需要先不做任何處理,會導(dǎo)致 React 內(nèi)部對于服務(wù)端內(nèi)容和客戶端內(nèi)容的「一致性檢測」失敗。
而利用 dangerouslySetInnerHTML 的特性,會讓 React 不再進一步 hydrate 遍歷 children 而是直接沿用服務(wù)端渲染返回的 HTML,保證在注水前渲染的樣式也是 OK 的。
再利用 suppressHydrationWarning 取消 React 對于內(nèi)容一致性檢測失敗的警告。
- export class Hydrator extends React.Component {
- componentDidMount() {
- new IntersectionObserver(async ([entry], obs) => {
- if (!entry.isIntersecting) return;
- obs.unobserve(this.root);
- const { load, ...props } = this.props;
- const Child = interopDefault(await load());
- ReactDOM.hydrate(
, this.root); - }).observe(this.root);
- }
- render() {
- return (
- ref={c => (this.root = c)}
- dangerouslySetInnerHTML={{ __html: '' }}
- suppressHydrationWarning
- />
- );
- }
- }
接下來,組件在客戶端初始化的時候,利用 IntersectionObserver 監(jiān)控組件元素是否進入視圖,一旦進入視圖了,才會動態(tài)的去 import 組件,并且利用 ReactDOM.hydrate 來真正的進行注水。
此時不光注水是動態(tài)化的,包括組件代碼的下載都會在組件進入視圖時才發(fā)生,真正做到了「按需加載」。
動圖中紫色動畫出現(xiàn),就說明漸進式 hydrate 完成了。
對比一下全量注水和漸進式注水的性能會發(fā)現(xiàn)首次可交互的時間被大大提前了:
當(dāng)然,我們了解原理就發(fā)現(xiàn),不光可以通過監(jiān)聽組件進入視圖來 hydrate ,甚至可以通過 hover 、 click 等時機來觸發(fā),根據(jù)業(yè)務(wù)需求的不同而靈活調(diào)整吧。
可以訪問圖片中的網(wǎng)址獲取你喜歡的框架在這方面的相關(guān)文章:
總結(jié)
本文通過總結(jié)了 Rendering on the Web: Performance Implications of Application Architecture (Google I/O ’19) [2] 這段 Google 團隊的精彩演講,來介紹了現(xiàn)代應(yīng)用架構(gòu)體系中的優(yōu)化手段,包括:
預(yù)渲染
同構(gòu)渲染
流式渲染
漸進式注水
在不同的業(yè)務(wù)場景下選擇對應(yīng)的優(yōu)化手段,是一名優(yōu)秀的前端工程師必備的技能,相信看完這篇文章的你一定有所收獲。
完整 demo 地址:
https://github.com/GoogleChromeLabs/progressive-rendering-frameworks-samples
網(wǎng)頁題目:Web 現(xiàn)代應(yīng)用程序架構(gòu)下的性能優(yōu)化,漸進式的極致藝術(shù)
文章路徑:http://www.5511xx.com/article/dhegehi.html


咨詢
建站咨詢
