初识React服务端渲染SSR

31次阅读

共计 2836 个字符,预计需要花费 8 分钟才能阅读完成。

服务端渲染

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"));

路由

正文完
 0