本文是直接着手SSR部分的并通过实战讲述自己遇到的一些问题和方案,需要大家有一定的React,node和webpack基础能力。skr,skr。
服务端渲染
Server Slide Rendering
服务端渲染,又简写为SSR
,他一般被用在我们的SPA(Single-Page Application)
,即单页应用。
为什么要用SSR?
首先我们需要知道SSR对于SPA的好处
,优势
是什么。
- 更好的
SEO(Search Engine Optimization)
,SEO
是搜索引擎优化,简而言之就是针对百度这些搜索引擎,可以让他们搜索到我们的应用。这里可能会有误区,就是我也可以在index.html上写SEO
,为什么会不起作用。因为React、Vue的原理是客户端渲染,通过浏览器去加载js、css,有一个时间上的延迟
,而搜索引擎不会管你的延迟
,他就觉得你如果没加载出来就是没有的,所以是搜不到的。 - 解决一开始的
白屏渲染
,上面讲了React的渲染原理,而SSR服务端渲染是通过服务端请求数据,因为服务端内网的请求快,性能好所以会更快的加载所有的文件,最后把下载渲染后的页面返回给客户端。
上面提到了服务端渲染和客户端渲染,那么它们的区别是什么呢?
客户端渲染路线:
- 请求一个html
- 服务端返回一个html
- 浏览器下载html里面的js/css文件
- 等待js文件下载完成
- 等待js加载并初始化完成
- js代码终于可以运行,由js代码向后端请求数据( ajax/fetch )
- 等待后端数据返回
- react-dom( 客户端 )从无到完整地,把数据渲染为响应页面
服务端渲染路线:
- 请求一个html
- 服务端请求数据( 内网请求快 )
- 服务器初始渲染(服务端性能好,较快)
- 服务端返回已经有正确内容的页面
- 客户端请求js/css文件
- 等待js文件下载完成
- 等待js加载并初始化完成
- react-dom( 客户端 )把剩下一部分渲染完成( 内容小,渲染快 )
其主要区别就在于,客户端从
无到有的
渲染,服务端是先在服务端渲染一部分
,在再客户端渲染一小部分
。
我们怎么去做服务端渲染?
我们这里是用express框架,node做中间层进行服务端渲染。通过将首页进行同构处理
,让服务端,通过调用ReactDOMServer.renderToNodeStream
方法把Virtual DOM
转换成HTML字符串
返回给客户端,从而达到服务端渲染的目的。
这里项目起步是已经做完前端和后端,是把已经写好的React Demo直接拿来用
服务端渲染开始
既然是首页SSR,首先我们要把首页对应的index.js
抽离出来放入我们服务端对应的server.js
,那么index.js
中组件对应的静态css和js文件
我们需要打包出来。
用webpack打包文件到build文件夹
我们来运行npm run build
我们可以看到两个重要的文件夹
,一个是js文件夹,一个是css文件夹,他就是我们项目的js和css静态资源文件
将打包后的build
文件能在服务端server.js
中访问到
因为是服务端,我们需要用到express
import express from 'express'
import reducers from '../src/reducer';
import userRouter from './routes/user'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import model from './model'
import path from 'path'
import https from 'http'
import socketIo from 'socket.io'
const Chat = model.getModel('chat')
//新建app
const app = express()
//work with express
const server = https.Server(app)
const io = socketIo(server)
io.on('connection',function(socket){
socket.on('sendmsg',function(data){
let {from,to,msg} = data
let chatid = [from,to].sort().join('_')
Chat.create({chatid,from,to,content:msg},function(e,d){
io.emit('recvmsg',Object.assign({},d._doc))
})
// console.log(data)
// //广播给全局
// io.emit('recvmsg',data)
})
})
app.use(cookieParser())
app.use(bodyParser.json())
app.use('/user',userRouter)
app.use(function(req,res,next){
if(req.url.startsWith('/user/') || req.url.startsWith('/static/')){
return next()
}
//如果访问url根路径是user或者static就返回打包后的主页面
return res.sendFile(path.resolve('build/index.html'))
})
//映射build文件路径,项目上要使用
app.use('/',express.static(path.resolve('build')))
server.listen(8088, function () {
console.log('开启成功')
})
- 主要看上面的
app.use('/',express.static(path.resolve('build')))
和res.sendFile(path.resolve('build/index.html'))
这两段代码。 - 他们把打包后的主页放入服务端代码中返回给客户端。
- 因为上面我用了
import
代码,所以我们在开发环境中需要用到babel-cli
里的babel-node
来编译。 - 安装
npm --registry https://registry.npm.taobao.org
i babel-cli -S`,大家如果觉得这样切换源麻烦,可以下个nrm,360度无死角切换各种源,好用! - 我们需要修改
package.json
的启动服务器的npm scripts
。"server": "NODE_ENV=test nodemon --exec babel-node server/server.js"
cross-env
跨平台设置node环境变量的插件。- nodemon和supervisor一样是watch服务端文件,只要一改变就会重新运行,相当于
热重载
。nodemon更轻量
- 最后我们来跑一下
npm run server
,就能看到服务端跑起来了。
ReactDOMServer.renderToString/ReactDOMServer.renderToNodeStream
- 这里我们先讲一下在
浏览器中
,React.createElement
把React的类进行实例化
,实例化后的组件可以进行mount
,最后通过React.render
渲染到我们的客户端浏览器界面。 - 而在服务器中我们可以通过
renderToString
或者renderToNodeStream
方法把React实例化的组件,直接渲染生成html标签。那么这俩个有什么区别呢? renderToNodeStream
是React 16最新发布的东西,它支持直接渲染到节点流。渲染到流可以减少你的内容的第一个字节(TTFB)
的时间,在文档的下一部分生成之前,将文档的开头至结尾发送到浏览器。 当内容从服务器流式传输时,浏览器将开始解析HTML文档。速度是renderToString的三倍
,所以我们在这里使用renderToNodeStream
import express from 'express'
import React from 'react'
import {renderToStaticMarkup,renderToNodeStream} from 'react-dom/server'
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import {StaticRouter} from 'react-router-dom'
import {
createStore,
applyMiddleware,
//组合函数用的
compose
} from 'redux';
import App from '../src/App'
import reducers from '../src/reducer';
import userRouter from './routes/user'
import bodyParser from 'body-parser'
import cookieParser from 'cookie-parser'
import model from './model'
import path from 'path'
import https from 'http'
import socketIo from 'socket.io'
const Chat = model.getModel('chat')
//新建app
const</