本文将简要介绍几个概念,如SPA, CSR, SSR, 前端路由。适合小白,我将用浅显又生动的话语来介绍这些概念。
什么是SPA
SPA(single page application)单页应用,占了现如今web应用的绝大多数。其意思就如字面所言,但是或许小白们不能真正理解何为单页,且让我讲给你听。我们先说,CSR(客户端渲染)和SSR(服务端渲染)都是SPA,我们先讲CSR的情况
从一个很经典的问题讲起,“当你在浏览器地址栏里输入了一个url,并且敲了回车之后发生了什么”。你只需要知道前端的代码需要部署在服务器上面(注意与作为ajax请求目标的后端接口服务器不同,本文的服务器均指代部署着前端代码的服务器),浏览器会向服务器发送一个请求,而服务器会返回给你html,css和js这些静态资源。多说一嘴为什么叫静态资源,就是因为它是不变的(每个人拿到的都是相同的),它就是你写的前端代码打包出来的产物。试想如果你完的代码会自己变化就太恐怖了。而所谓的动态通常指依赖接口返回的数据(通常不同的用户拿到的也不同)。
而对于SPA来说,相信无论是用vue或者react的各位都还记得,项目的入口html文件里面有个div,id可能是root或者app,它就是页面的根节点。而vue或者react框架,就是写一堆js去生成dom元素,然后把生成的dom元素塞到根节点里面(挂载到根节点下)。如果你打开一个CSR(客户端渲染)的SPA应用,去查看控制台,network里面的Doc下面,有个很简洁的html文件,这个就是服务器传给浏览器的html文档。这个文档,也就是单页应用中的那个单页。
而单页,单就单在,这个网站的各个界面,点击跳转之后的界面,都是渲染在最初的这一份html文档上面的。打个比方,把网站当成一幅画,最初的那份html就是画布。你先在上面画了一个myWebSite.com/home,然后你要跳转到myWebSite.com/about,不是再拿一幅画布,而是把原来的画布擦干净,画一幅新的上去。这也就是前端路由的概念。在SPA中,当你去做一些跳转的操作时,不会再向服务器发送请求新的html文档,而是通过js的操作,去更新html上的内容。
关于前端路由的具体实现,本文不具体讲解,我们只需要知道前端路由是在不重新向服务器请求页面的前提下。改变了url。然后通过一些api,根据url的变化,去渲染不同的组件,从而使得我们能看到不同的页面。当然需要注意,虽然url被改变了,但是浏览器没有向服务器发送请求。如果你这时候刷新页面,则浏览器会试图向服务器发送请求,某些情况下会拿到404响应码,比如vue的history模式路由。
我们回顾SPA的渲染过程,它只在你第一次输入URL并且敲回车的时候请求一次服务器,拿到所有的html,css,js之后,浏览器开始完全接管页面的渲染和交互。通过执行js,将你写的组件挂载到根节点当中。当你试图在应用内(也就是你写的项目里)跳转时,你写的js会在原有html上渲染出新的组件,而你的交互也都是通过浏览器去执行js来实现。这就是CSR(客户端渲染),页面的渲染都是在浏览器上完成的。
什么是SSR
大部分小白们可能刚接触vue和react的时候写的都是CSR,也就是上面提到的内容。CSR有一些缺点,比如FCP(First Contentful Paint)首次内容绘制时间长,说白了就是首屏的白屏时间长(相对较长)。因为一开始浏览器拿到的只有一份空的html,所以用户看到的是白屏。你写的组件要等到浏览器去执行js去渲染到html上。假设浏览器执行js要10秒钟,那用户就会看到10秒的白屏,认为你这个网站好卡啊。
当然这是CSR的缺点之一,其次还有不利于SEO,说白了就是不容易被搜索引擎给抓取到,因为搜索引擎也会认为你这个网站是个空的html(搜索引擎也有做优化,稍有改善)。
为了解决这些问题,人们采用了SSR(服务端渲染)的解决方案。其实最早前后端没分离的时候,就是由后端服务器将动态的数据塞到html模板里面,返回给前端的。当你输入url按下回车之后,浏览器拿到的就是一份完整的有内容的html,而不是空的html。当然这是古早时期的行为了,不在本文讨论范围内。
为了解决CSR由浏览器绘制页面导致的白屏时间长的问题,SSR直接由服务端将你的组件渲染好塞到html文档里面,一起返回给你。说白了,和客户端渲染的区别就是,第一次拿到的是带内容的html文档,css和js。浏览器拿到带内容的html文档,就可以直接展示给用户看了。然后浏览器再执行js,将点击事件之类的添加到页面上,不然你的页面就只能看,没有任何交互了。
举个服务端渲染的例子,我想访问myWebSite.com/home,,服务器会渲染好一个home的html(html当然只能看不能点),连带着css,js返回给浏览器,之后浏览器在执行js。我们以最流行的React框架Next为例讲解,对于静态的页面,那可能如果myWebSite.com/home依赖动态数据,那么你可以在getServerSideProps方法里面去获取,然后将数据作为方法的返回值,这样一来你的页面就能从props拿到动态的数据。比如/home页面可能需要从接口拿用户信息,你可以选择普通的将调用接口写在组件的生命周期里(由浏览器执行js的时候调用),也可以将调接口写在getServerSideProps里(由服务端调用),再讲接口的数据传给页面组件。
那么服务端渲染的前端路由切换是怎么样的呢,还是以next为例,我们这里只讲解getServerSideProps的情况,这种最复杂有趣,其他情况请参见next文档。当你在next中使用前端路由跳转到一个通过getServerSideProps来进行服务端渲染的页面时,你要记得你依然是一个单页应用,next会支持你在原有的html文档上面切换要渲染的组件。但是你的页面是依赖getServerSideProps来给你提供数据的,而getServerSideProps不能在浏览器调用。这下困难了,不能像CSR那样不借助服务器就完成页面的切换了。这个时候next框架会替你向服务器发送一份请求,求助服务器帮忙执行一下新页面的getServerSideProps方法,并且将结果以json格式返回给浏览器。这个请求的url是形如myWebSite.com/{配置的basepath}/_next/data/{generateBuildId}/{query}/{页面的名字}.json
。如果这个请求挂掉了(不常见的原因,比如我司配的软路由不恰当,将这个请求导向了错误的地址),那么next的补救措施是,让浏览器重新向服务器发送请求,有服务器渲染一个新的要跳转到的页面,再返回给浏览器,相当于重新执行了一遍最初的工作。
SSR也有缺点,比如每当浏览器重新向服务器请求的时候,都会请求很多的数据,包括当前页面的带内容的html,所有页面的css,所有页面的js(不考虑代码分割,懒加载的情况)。每当SSR的前端路由请求挂掉时,SSR都要重新请求大量的数据。而相对的,CSR只有首次渲染依赖前端服务器,而且这一次请求的内容是不带内容的html,所有页面的css,所有页面的js。这样看来哪怕是单次比较,CSR请求的东西也是比SSR少的,也就是说CSR虽然首次绘制出内容要晚于CSR,但是因为网络传输的内容少,所以会更早拿到请求的内容,从而更早执行完js,使得用户可以交互。而SSR虽然一开始拿到了有内容的html,让用户更早看到页面,但是要传输的内容多,从而晚执行完js,使得用户更晚才能交互。这个指标叫TTI(Time To Interactive),首次可交互时间。
所以SSR和CSR其实各有优缺点,使用的时候需要综合考量。