揭开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。

相关文章
相关标签/搜索