页面由服务端直接返回给浏览器,路由为服务端路由,URL 的变动会刷新页面,原理与 ASP,PHP 等传统后端框架相似。javascript
页面在 JavaScript,CSS 等资源文件加载完毕后开始渲染,路由为客户端路由,也就是咱们常常谈到的 SPA(Single Page Application)。css
编写的 JavaScript 代码可同时运行在浏览器及 Node.js 两套环境中,用服务端渲染来提高首屏的加载速度,首屏以后的路由由客户端控制,即在用户到达首屏后,整个应用还是一个 SPA。html
在明确了这三种渲染方案的具体含义后,咱们能够发现,不管是客户端渲染仍是服务端渲染,都有着其明显的缺陷,而同构显然是结合了两者优势以后的一种更好的解决方案。前端
渲染模式 | 优势 | 缺点 |
---|---|---|
CSR | 网络传输数据量小、减小服务器压力、先后端分离、局部刷新、无需每次请求完整页面、交互好可实现各类效果 | 不利于 SEO、爬虫看不到完整的程序源码、首屏渲染慢 |
SSR | 首屏渲染快、利于 SEO、能够生成缓存片断、生成静态文件、节能(相比客户端渲染的耗电) | 用户体验较差、不易维护 |
SSR 的工程中,React 代码会在客户端和服务器端各执行一次。你可能会想到,这没什么问题,由于都是 JavaScript 代码,既能够在 Node 执行,也能够在浏览器上运行。可是若是你的代码操做了 DOM,那就有问题了。由于 Node 没有 DOM。java
好在 React 引入虚拟 DOM 的概念,虚拟 DOM 是真实 DOM 的一个 JavaScript 对象,React 在作页面操做时,实际上不是操做真实 DOM,而是操做虚拟 DOM,也就是操做普通对象。在服务器,我能够操做 JavaScript 对象,判断环境是服务器环境,咱们把虚拟 DOM 转换成字符串输出;在客户端,也一样能够判断是客户端环境,直接将虚拟 DOM 转成真实 DOM,完成页面渲染。react
因为 SSR
的代码与 CSR
的代码不同,因此咱们新建 webpack.server.js
文件而且修改 React
代码。webpack
假使你已经拥有 express 服务器,里面代码以下:web
var express = require('express');
var app = express();
app.get('/', (req, res)=>{
res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Hello Wrold</title> </head> <body> <div>Hello Wrold</div> </body> </html> `)
})
app.listen(8569)
复制代码
启动代码,打开 localhost:8569 能够看到页面上显示了 Hello Wroldexpress
修改 webpack 与 react 代码json
libraryTarget
配置如何暴露 library。umd
意味着将你的 library 暴露为全部的模块定义下均可运行的方式。它将在 CommonJS、AMD 环境下运行,或将模块导出到 global 下的变量。
// webpack
...
module.exports = {
mode: 'production',
entry: './src/module/demo.js',
output: {
filename: 'demo-server.js',
path: path.join(__dirname, 'dist'),
libraryTarget: 'umd'
},
...
}
复制代码
//react
const React = require('react');
class Box extends React.Component{
render(){
return (
<div>This is a box</div>
)
}
}
module.exports = <Box /> 复制代码
修改 express 代码
Node 没有 window,须要 hack
if(typeof window === 'undefined'){
global.window = {}
}
...
const SSR = require('../dist/demo-server.js');
const { renderToString } = require('react-dom/server');
app.get('/', (req, res)=>{
res.end(` ... <body> ${renderToString(SSR)} </body> ... `)
})
app.listen(8569)
复制代码
启动代码,打开 localhost:8569,你会看到下图所示。
你们都知道 Node 没有样式解析,也没有 XMLHttpRequest,这些咱们在作 SSR 的时候应该怎么处理?咱们能够用客户端打出来的代码部署静态服务器,在 .html
的代码里面加入占位符,而后在服务器端作占位符替换。这样,当客户端访问页面的时候,咱们经过外链加载样式,再经过插入 json 对象来获取初始页面数据。
外链加载样式部分代码
// 打包后的 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>demo</title>
<link href="main.77f6fd9c.css" rel="stylesheet"></head>
<body>
<div id="root"><!-- html-placeholder --></div>
<script type="text/javascript" src="https://lib.baomitu.com/react/16.8.6/umd/react.development.js"></script><script type="text/javascript" src="https://lib.baomitu.com/react-dom/16.8.6/umd/react-dom.development.js"></script><script type="text/javascript" src="demo-server.js"></script></body>
</html>
复制代码
// express
if(typeof window === 'undefined'){
global.window = {}
}
const express = require('express');
const app = express();
const SSR = require('../dist/demo-server.js');
const { renderToString } = require('react-dom/server');
const fs = require('fs');
const path = require('path');
const template = fs.readFileSync(path.join(__dirname, '../dist/demo.html'), 'utf-8');
app.use(express.static('../dist/'))
app.get('/', (req, res)=>{
let html = template.replace('<!-- html-placeholder -->', renderToString(SSR));
res.end(html)
})
app.listen(8569)
复制代码