頁面由服務端直接返回給瀏覽器,路由爲服務端路由,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)
複製代碼