您好,登錄后才能下訂單哦!
這篇“React SSR架構Stream Rendering與Suspense for Data Fetching源碼分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“React SSR架構Stream Rendering與Suspense for Data Fetching源碼分析”文章吧。
假設我們的業務背景如下:
我們的頁面分為兩大塊,上面部分是個人介紹,依賴接口 /api/profile
(耗時約 3s),下面部分是文章列表,依賴接口 /api/list
(耗時約 6s)。其中文章列表的業務邏輯非常重,代碼體積很大,依賴的接口比較慢。
我們先來看下傳統的 SSR (服務端獲取到所有接口數據后調用 renderToString
渲染出內容返回給前端,同時在頁面中插入全局的 INITIAL_STATE 供客戶端注水)和基于 Stream Rendering & Suspense for Data Fetching (以下簡稱 Stream SSR)兩者的效果對比。首先,我們來對比下從用戶發起請求到用戶看到內容這個階段。傳統 SSR 用戶看到的都是一個空白頁面,一直要等到最耗時的 /api/list
接口返回用戶才能看到內容。而 Stream SSR 有以下幾點的提升:
在頁面內容返回前有 loading 的提示
Profile
的內容先處理完,先返回,沒有被 List
阻塞
同樣的,注水過程也是如此。傳統 SSR 需要等到 JS 加載完后,統一對整個應用進行注水。而 Stream SSR 則先完成了 Profile
的注水。
那么,要怎么實現這樣的效果呢?接下來讓我們 step by step。或者直接看代碼。
首先,為了實現 Stream Rendering,我們需要使用 renderToPipeableStream
,假設我們有如下 HTML 模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>SSR + MicroFrontend</title> </head> <body> <div id="app1"><!-- app1 --></div> <script crossorigin src="http://localhost:8080/dist/client.js"></script> </body> </html>
則我們可以按如下方式進行返回:
app.get('/', async (req, res) => { const [heal, tail] = html.split('<!-- app1 -->') const stream = new Writable({ write(chunk, _encoding, cb) { res.write(chunk, cb) }, final() { res.end(tail) }, }) const {pipe} = renderToPipeableStream(<App />, { onShellReady() { res.statusCode = 200 res.write(head) pipe(stream) }, }) })
看著有點奇怪是吧,這是因為 renderToPipeableStream
的返回不再是 Node.js 中的 ReadableStream
對象,無法監聽 end
事件。所以這里通過一個中間的 Writable
對象來轉接數據,并監測渲染流的結束。
Stream Rendering 的部分搞定了,接下來我們看看 Data Fetching 部分。
在這篇文章曾經提到過結合 Suspense
做 Data Fetching,但是之前是自己實現的一個簡單的請求工具,為了更貼近實際,這次使用 react-query
。則組件中可以按照如下方式來請求數據:
async function getList() { const rsp = await fetch('http://localhost:9000/api/list') const data = await rsp.json() return data } const List = () => { const query = useQuery(['list'], getList) return ( <ul> {query.data.map((item) => ( <li key={item.name}>{item.name}</li> ))} </ul> ) }
在使用該組件的時候,可以用 Suspense
包裹起來,以便于數據返回前用戶可以看到一個 loading 的效果:
const App = () => { return ( <div> <Suspense fallback={<p>Loading List...</p>}> <List /> </Suspense> ... </div> ) }
同時為了減少入口文件的體積,我們通過異步的方式來引入 List
這個比較大的組件:
const List = React.lazy(() => import('./List'))
類似的,Profile
組件也可以按照同樣的方式來處理。
這樣,Stream Rendering & Suspense for Data Fetching 基本上算是實現了。不過現在還有個問題,對于每個組件,我們會分別在服務端和客戶端都請求一次接口。正確的做法應該是只在服務端請求一次,然后服務端返回 HTML 的時候把接口數據也一并帶上,作為 CSR 的初始數據。
React Query 官網中有介紹 SSR 相關的內容,但是跟傳統的 SSR 沒什么區別,也是要等到數據都獲取完后,才開始渲染:
function handleRequest (req, res) { const queryClient = new QueryClient() await queryClient.prefetchQuery('key', fn) const dehydratedState = dehydrate(queryClient) // 得到一個接口請求的全局狀態 const html = ReactDOM.renderToString( <QueryClientProvider client={queryClient}> <Hydrate state={dehydratedState}> <App /> </Hydrate> </QueryClientProvider> ) res.send(` <html> <body> <div id="root">${html}</div> <script> window.__REACT_QUERY_STATE__ = ${JSON.stringify(dehydratedState)}; </script> </body> </html> `) queryClient.clear() }
這樣的做法有幾個缺點:
整個應用的渲染都被阻塞了,原本可以更早返回的 Profile
也被推遲了
必須要知道當前頁面渲染所需要調用的所有接口,當頁面很復雜且由多人維護時這個代碼就很不好維護了
下面我們來解決這些問題,最終的方案我稱之為“全局狀態動態更新”方案。
從上面的代碼可以知道,通過 dehydrate(queryClient)
可以得到一個全局的對象用來描述當前請求得到的數據,那我們是不是可以在組件里面每次有數據獲取到時就來更新一下這個對象呢?就像這樣:
const query = useQuery(['data'], getList) const ee = useContext(EventEmitterContext) if (ee && query.data) { ee.emit('updateState') }
然后我們在處理請求的回調函數中監聽這個事件,更新全局狀態:
const templateDOM = new JSDOM(` <!DOCTYPE html> <html lang="en"> <head> ... </head> <body> <div id="app1"><!-- app1 --></div> <script id="reactQueryState">window.__REACT_QUERY_STATE__ = ${JSON.stringify( dehydratedState )};</script> ... </body> </html> `) ... ee.on('updateState', () => { const dehydratedState = dehydrate(queryClient) templateDoc.querySelector( '#reactQueryState' ).innerHTML = `window.__REACT_QUERY_STATE__ = ${JSON.stringify( dehydratedState )};` })
這樣我們就做到了仍然流式的返回內容給用戶,并在這個過程中不停的更新全局數據,最后返回給客戶端,而且也不需要了解這個頁面渲染所需要的所有接口,即保證了用戶體驗,又沒有丟失代碼的可維護性。
以上就是關于“React SSR架構Stream Rendering與Suspense for Data Fetching源碼分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。