揭開React中server-side rending的神祕面紗

原文地址: https://medium.freecodecamp.o...

image
Server-Side Rendering :SSR 是一種前端框架可以在後端渲染出HTML的能力。那些可以在客戶端和服務端完成渲染的應用就叫作universal apphtml

爲何須要SSR?

爲了理解爲何須要SSR,這裏咱們須要瞭解下web應用在過去十年內的發展史。SSR與SPA(Single Page Application)的興起緊密相連。與傳統的服務端渲染的app相比,SPA在速度和用戶體驗發麪存在很大的優點。
可是使用SPA有個問題,一般狀況下用戶第一次請求會返回一個空html文件和一堆JS和CSS連接,渲染html以前會先把JS和CSS提早下載下來。
這就意味着首次渲染的時候,用戶必需要等更長的時間。同時對於爬蟲來講,解析到的頁面也是一個空頁面。
所以SSR的主要思想就是首次在server端渲染應用,後續能夠充分利用SPA的優點,在客戶端完成渲染。
SSR + SPA = Universal App
有的文章中也會把Universal App講成isomorphoic app,實際上這兩個是同一個東西。採用SSR的狀況下,首次渲染的時候,用戶不須要等JS加載完成後在看到渲染完成的頁面,而是在請求返回的時候就已經拿到渲染完成的頁面了。前端

對於使用slow 3G的用戶來講,使用SSR會大大改善用戶體驗。用戶將會直接看到網頁內容而不是等待20s+才能看到網頁內容。
image
如今狀況下,全部發送到server端的請求都會被直接返回成HTML。這樣對於作SEO的部門來講也是十分有利的。
對爬蟲來講不會區別對待SPA引用和其餘靜態站點,一樣會爲服務端渲染的內容生成索引。
簡而言之,使用SSR有兩點好處:react

  • 首次渲染速度更快
  • 生成的HTML內容能夠被索引到。

一步步來理解SSR

下面筆者將經過一個例子,一步步來實現一個完整的SSR案例。首先從React的服務端渲染API開始,後續每一步咱們都將加入一些新的內容。
能夠follow 這個項目倉庫 ,每一步的代碼都會有一個tag,讀者能夠經過git checkout tags/xxx -b xxx的方式獲取每一步的代碼(xxx爲對應的tag名)。git

Basic Setup

開始介紹SSR以前,咱們須要一個server。這裏筆者採用express來渲染React應用。
imagegithub

在代碼的第10行,咱們用express啓動了一個靜態服務器。同時咱們也建立了一個用於處理非靜態請求的handle函數。非靜態的路由將會返回HTML代碼。web

在代碼的第13~14行,咱們用renderToString 函數把一開始的JSX代碼轉換成字符串,這段字符串後續將被插入到HTML模板中。express

PS:咱們並無直接啓動sever.js ,而是經過index.js來啓動server.js。在index.js中,咱們用babel插件來抹平client和server端的差別,保證client和server都可以使用es module和jsx。redux

在SSR中client端的代碼也須要從ReactDOM.render的改爲ReactDOM.hydrate。這個函數將會接受服務端渲染的react代碼並掛載事件處理函數。
image後端

想看到完整的例子,能夠check react-ssr tag爲basic的代碼。到這兒爲止,咱們就完成了一個簡單的服務端渲染的react app。promise

React Router

到目前爲止,咱們的應用實際上啥事也沒幹。如今咱們來往以前的應用加入一些路由。先來看看如何處理服務端部分:
image

如今Layout組件在client端上將會渲染出路由組件。對應的咱們須要在server端模擬出client的路由實現。下面咱們列出server端代碼的修改部分:
image

在服務端的代碼中,咱們須要把React Application包裝在StaticRouter 組件中,並提供location參數。
PS:context用於在渲染React DOM的過程當中追蹤可能的重定向請求:好比client須要根據3XX響應重定向。
完整的案例須要checkout tag爲router的代碼。

Redux

在項目已經具有路由能力的狀況下,下面咱們來集成redux。一些場景下,咱們須要使用redux來管理client端的狀態。可是在服務端渲染的狀況,如何根據當前狀態來渲染部分DOM是個問題,所以咱們有必要在服務端初始化redux。
若是應用在服務端dispatch action的狀況下,SSR須要記錄下這些操做,並把最終的state和HTML一塊兒返回給client。在client端,會把服務端返回的state設爲redux的初始狀態。

咱們先來看看server端的實現:
image

這段代碼看起來實現的十分醜陋,可是咱們確實須要把服務端渲染出來的redux狀態和HTML代碼一塊兒返回給client。
接着來看看client部分的實現:
image

這裏咱們調用了兩次createStore,一次在server端,一次在client端。可是在client端上須要把server端保存下來的狀態設爲redux的初始狀態。

完整的例子能夠看當前項目的redux tag。

Fetch Data

最後一步就是加載數據。這是個比較棘手的問題。咱們從一個返回JSON數據的接口開始講起。
在代碼倉庫中,筆者經過開放API獲取了2018第一賽季的Formula的數據。咱們但願在Home頁面顯示全部的Formula數據。

咱們能夠在全部React app掛載完成、全部元素都已經渲染完畢的的狀況下調用API接口來完成需求。若是這樣的話,可能會存在一些loading畫面,對於用戶體驗並不友好。

考慮到項目中已經整合了Redux,咱們能夠經過Redux來保存數據並返回給前端的方式來加載數據。
如何在server端調用API接口、將接口返回數據保存在Redux中並讓客戶端根據相關數據來渲染HTML呢?
那麼須要調用哪些接口呢?
首先咱們須要經過一種不一樣的方式來申明路由。因此咱們把路由改爲以下所示:
image

同時咱們也須要在組件上聲明全部的數據:
image

PS: fetchData 是一個 Redux thunk action,dispatch fetchData的時候會返回一個promise
同時在服務端,咱們也用了一個react-router中的特殊的函數:matchRoute
image

經過這個方法,當服務端根據當前URL渲染頁面的時候會獲得須要被mounted 的組件。咱們會收集全部組件須要的數據,等待全部接口都已經返回數據,並把獲取的數據塞到redux中才會繼續執行服務渲染。
切換到tag爲fetch-data的分支能夠看到整個案例。

從這兒開始,咱們就會開始從各個維度進行比較,並比較出哪些場景適合使用SSR哪些場景不適合使用SSR。好比說對一個電商app來講,獲取全部的產品是重中之重,可是價格以及一些其餘的邊欄filter相比之下就顯得不那麼重要。

Helmet

最後咱們來看看SEO。當和React打交道的時候,咱們常常須要在<head>標籤中設置不一樣的值。好比:titlemeta tags keywords 等等。
記住<head> 標籤中的內容通常不是React App的一部分。
react-helmet就是爲了解決修改<head>標籤中的內容而生的,並對SSR提供了良好的支持。
image

你能夠在組件樹中的任何地方加入head標籤內的數據。在client端上,react-helmet提供了一種修改React App之外部分的能力。
咱們也在SSR中加入這種能力:
image

如今咱們已經顯示了一個具有基礎功能的React服務端渲染的案例。咱們從一個返回HTML內容的express應用開始,慢慢加入了路由、狀態管理以及獲取數據的能力。最後咱們還處理React App以外的部分。完整的代碼在master分支能夠看到。

Conclusion

正如本文所示,SSR並不適合一件難事,可是SSR也能夠作的很複雜。若是一步一步來實現須要會更容易些。那麼項目中是否須要加入SSR呢?具體狀況具體分析。若是網站訪問量很大,則建議作SSR。可是若是你的應用是相似於工具或者dashboard這種應用,則不必花費較多的精力來實現SSR。

相關文章
相關標籤/搜索