使用react这么久 ,都是基于客户端渲染,闲来无事,想去玩玩基于react的服务器端渲染。
客户端渲染的问题:
- 用户初次访问的体验不好
- 对 SEO 不友好
客户端渲染的页面通常是很简洁:
<!doctype html>
<html>
<head>
<title>这是一个单页面应用</title>
</head>
<body>
<div id='root'></div>
<script src='app.js'></script>
</body>
</html>
访问该页面,浏览器便开始解析 HTML 并加载 app.js 文件,加载完成浏览器执行 js,接着 <div id='root'></div> 下会生出许多节点,并且具备交互能力。
但是这里会有两个问题:
- 在 js 文件加载完成并执行以前,我们只看到一片空白,
- 一旦 js 文件中有错误,也可能导致页面空白
无论哪种情况,对用户来说,都是糟糕的体验。
而服务端渲染能改善用户的体验:
- 用户能马上看到页面内容 - 而不是等待 js 文件下载、执行完成后才能看到,
- 哪怕 js 中有错误,也只会导致页面交互问题,而不是一片空白。
在seo层面,我们希望搜索引擎能够抓取完整的HTML文件,而不是上述中的<div id='root'></div>,服务器端渲染则能够提供完整的 HTML 内容给搜索引擎,对 SEO 更友好。
服务器端渲染流程
传统的服务器端渲染大致是一个这样的流程:
- 从后台数据中读取信息
- 编译模板生成HTML代码
- 返回HTML给客户端
- 浏览器解析html并下载html页面中的脚本文件然后执行
React.js的服务器端渲染也是如此,只不过这里的模板换成了react组件,js事件的绑定由React.js 操作。
服务器端如何渲染React.js
我们将使用 ReactDOMServer 提供的 renderToString 方法在服务器端渲染 React.js 组件。下面将简单的搭建一个基于React.js的服务器端渲染的小demo。
$ mkdir react-server-render
$ cd react-server-render
$ npm init -y
$ npm i react react-dom --save
在项目目录中新增index.html文件
<!doctype html>
<html>
<head>
<title>React 服务器端渲染</title>
</head>
<body>
<div id='root'></div>
<script src='dist/main.js'></script>
</body>
</html>
然后在react-server-render 下新建一个 src 目录,在 src 下新建 index.js 以及 App.js,两个文件的内容分别如下:
index.js:
const React = require('react')
const ReactDOM = require('react-dom')
const App = require('./App')
ReactDOM.render(React.createElement(App), document.getElementById('root'))
App.js:
const React = require('react')
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
color: false
}
}
render () {
const self = this
return React.createElement('div', {
onClick: function () {
self.setState({
color: !self.state.color
})
},
style: {
color: this.state.color ? 'red' : 'black'
}
}, 'hello react')
}
}
module.exports = App
接下来我们需要webpack打包index.js。
$ npm install webpack webpack-cli --save-dev
接着运行 webpack:
$ npx webpack --mode development (webpack 4新做的改进)
现在访问index.html,我们就能看到客户端渲染出的内容了。
然后我们搭建一个简单的node.js的服务器,在 react-server-render 目录下新建一个 index.js 文件:
const http = require('http')
const server = http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' })
response.write('hello react')
response.end()
})
server.listen(3000)
执行 node index.js,我们就能在 http://localhost:4200 访问页面 - 看到写死的 hello react.js。
那要如何返回 html 文件?
也很简单,我们读取 html 文件并返回即可:
const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' })
fs.createReadStream(path.join(__dirname, 'index.html')).pipe(response)
})
server.listen(3000)
但上面的代码有一个问题,就是所有的请求都会返回 index.html 的内容,不管它是 / 还是 dist/main.js。我们需要优化上面的代码,区分不同的请求:
const http = require('http')
const fs = require('fs')
const path = require('path')
const server = http.createServer(function (request, response) {
if (request.url === '/') {
response.writeHead(200, { 'Content-Type': 'text/html' })
fs.createReadStream(path.join(__dirname, 'index.html')).pipe(response)
} else {
if (/\.js/.test(request.url)) {
response.writeHead(200, { 'Content-Type': 'application/javascript' })
fs.createReadStream(path.join(__dirname, `${request.url}`)).pipe(response)
} else {
response.writeHead(404)
response.end()
}
}
})
server.listen(3000)
好了,我们成功运行一个 Node.js 应用,它能够返回 html 文件,也能返回 JavaScript 文件。当然,大部分时候我们不需要写原生的 Node.js 代码,而是使用 Express.js 等成熟框架。同理,我们可以返回服务器端渲染的 React 代码, 我们要用到 ReactDOMServer 提供的 renderToString。
const http = require('http')
const fs = require('fs')
const path = require('path')
const ReactDOMServer = require('react-dom/server')
const React = require('react')
const App = require('./src/App.js')
function renderHTML (body) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<title>React 服务器端渲染</title>
</head>
<body>
<div id="root">${body}</div>
<script src="dist/main.js"></script>
</body>
</html>
`
}
const server = http.createServer(function (request, response) {
if (request.url === '/') {
let body = ReactDOMServer.renderToString(React.createElement(App))
response.writeHead(200, { 'Content-Type': 'text/html' })
response.write(renderHTML(body))
response.end()
} else {
if (/\.js/.test(request.url)) {
response.writeHead(200, { 'Content-Type': 'application/javascript' })
fs.createReadStream(path.join(__dirname, `${request.url}`)).pipe(response)
} else {
response.writeHead(404)
response.end()
}
}
})
server.listen(3000)
如上,一个简单的基于react 的服务器端渲染已经完成。
通过控制台模拟Slow 3G的网络环境,可以明显发现,通过客户端渲染的时候,会产生白屏现象,而通过服务器端渲染,
无白屏现象。作为前端小白,在我理解的是,可以客户端渲染为主,服务器端渲染为辅,用服务器端渲染进行页面的首屏加载,这
样,不仅会加快渲染速度,也不会造成服务器端太大的压力。
参考:
https://reactjs.org/docs/react-dom-server.html
人真是懒惰的生物。好久没有认真的开始学习,生活好像没有了目标。