服务端渲染
Server Slide Rendering服务端渲染,又简写为SSR,他一般被用在我们的SPA(Single-Page Application),即单页应用。
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串(在Node环境已经跑了一遍JS拿到该拿的数据),然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。
为什么要用SSR
更好的SEO(Search Engine Optimization)
SEO是搜索引擎优化,简而言之就是针对百度这些搜索引擎,可以让他们搜索到我们的应用
事实上,很多网站是出于效益的考虑才启用服务端渲染,性能倒是在其次。
假设 A 网站页面中有一个关键字叫“前端性能优化”,这个关键字是 JS 代码跑过一遍后添加到 HTML 页面中的。那么客户端渲染模式下,我们在搜索引擎搜索这个关键字,是找不到 A 网站的——搜索引擎只会查找现成的内容,不会帮你跑 JS 代码。A 网站的运营方见此情形,感到很头大:搜索引擎搜不出来,用户找不到我们,谁还会用我的网站呢?为了把“现成的内容”拿给搜索引擎看,A 网站不得不启用服务端渲染。
但性能在其次,不代表性能不重要。服务端渲染解决了一个非常关键的性能问题——首屏加载速度过慢。在客户端渲染模式下,我们除了加载 HTML,还要等渲染所需的这部分 JS 加载完,之后还得把这部分 JS 在浏览器上再跑一遍。
提升首屏加载速度
更好的用户体验,对于缓慢的网络情况或运行缓慢的设备,加载完资源浏览器直接呈现,无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的HTML。
客户端渲染和服务端渲染路线对比
客户端渲染路线:
1.请求一个html
2.服务端返回一个html
3.浏览器下载html里面的js/css文件
4.等待js文件下载完成
5.等待js加载并初始化完成
6.js代码终于可以运行,由js代码向后端请求数据( ajax/fetch )
6.等待后端数据返回
7.react-dom( 客户端 )从无到完整地,把数据渲染为响应页面
服务端渲染路线:
1.请求一个html
2.服务端请求数据( 内网请求快 )
3.服务器初始渲染(服务端性能好,较快)
4.服务端读取浏览器端打包好的index.html文件为字符串,将渲染好的组件、样式、数据塞入html字符串,返回给浏览器
5.客户端请求js/css文件
6.等待js文件下载完成
7.等待js加载并初始化完成
8.浏览器直接渲染接收到的html内容,并且加载打包好的浏览器端js文件,进行事件绑定,初始化状态数据,完成同构
简易的React服务端渲染
renderToString
React可以将React元素渲染成它的初始化Html,并且返回html字符串,在express服务端生成html,返回给浏览器渲染
const express = require('express');
const app = express();
const React = require('react');
const {renderToString} = require('react-dom/server');
const App = class extends React.PureComponent{
render(){
return React.createElement("h1",null,"Hello World");;
}
};
app.get('/',function(req,res){
const content = renderToString(React.createElement(App));
res.send(content);
});
app.listen(3000);
同构
将上面的代码加上JS的事件监听服务器端渲染返给浏览器,你会发现在浏览器里无能如何点击都不会触发事件
因为renderToString只是返回html字符串,元素对应的js交互逻辑并没有返回给浏览器,因此点击h1标签是无法响应的。
const App = class extends React.PureComponent{
handleClick=(e)=>{
alert(e.target.innerHTML);
}
render(){
return <h1 onClick={this.handleClick}>Hello World!</h1>;
}
};
解决方法之前,我们先讲一下“同构”这个概念。何为“同构”,简单来说就是“同种结构的不同表现形态”。
同一份react代码在服务端执行一遍,再在客户端执行一遍。
同一份react代码,在服务端执行一遍之后,我们就可以生成相应的html。在客户端执行一遍之后就可以正常响应用户的操作。这样就组成了一个完整的页面。所以我们需要额外的入口文件去打包客户端需要的js交互代码。
import express from 'express';
import React from 'react';
import {renderToString} from 'react-dom/server';
import App from './src/app';
const app = express();
app.use(express.static("dist"))
app.get('/',function(req,res){
const content = renderToString(<App/>);
res.send(`
<!doctype html>
<html>
<title>ssr</title>
<body>
<div id="root">${content}</div>
<script src="/client/index.js"></script>
</body>
</html>
`);
});
app.listen(3000);
“/client/index.js”就是我们用webpack打包出来的用于客户端执行的js文件
现在点击会出现弹窗
ReactDOM.hydrate()
我们在服务端渲染时用ReactDOM.hydrate()来取代ReactDOM.render()
ReactDOM.render()会将挂载dom节点的所有子节点全部清空掉,再重新生成子节点。而ReactDOM.hydrate()则会复用挂载dom节点的子节点,并将其与react的virtualDom关联上。
所以我们客户端入口文件调整一下,拿到刚才从后台返回HTML里面的root节点,进行hydrate
import React from 'react';
import {hydrate} from 'react-dom';
import App from './app';
hydrate(<App/>,document.getElementById("root"));
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。