< Back

React服务端渲染

  • Hello World

首先Node环境肯定要有,然后下面这一堆

npm i react npm i react-dom # 让他支持es6 npm install babel-cli -g npm install babel-preset-react -S npm install babel-preset-env -S npm install babel-plugin-transform-decorators-legacy -S npm install express --save npm install --save-dev cross-env # 跨平台设置环境变量 npm install -g nodemon # 监视文件的改变并重新运行命令

装完后,package.json中设置运行命令:

"scripts": { "serve": "cross-env NODE_ENV=test nodemon --exec babel-node src/server.js" }

项目根目录新建.babelrc,配置插件这些的

{ "presets": [ "env", "react" ], "plugins": [ "transform-decorators-legacy" ] }

src/server.js

import express from 'express' var app = express() app.get('/', (req, res) => { res.send('<h1>hello world</h1>') }) app.listen(4000, () => { console.log('server started') })

npm run serve 后localhost:4000就能看到Hello World了

  • react服务器端渲染的实现

react-dom/server包里有两个方法renderToString和renderToStaticMarkup,这个两个方法都是React Comonent转化为HTML字符串。React 15中有如下区别,但是React 16中已经废弃了data-react-checksum等属性。

区别在于renderToString生成的HTML的DOM会带有额外属性:各个DOM都会有data-react-id属性,第一个DOM会有data-checksum属性。renderToStaticMarkup不会有这些额外属性,从而节省HTML字符串大小

15中当重新渲染节点时,ReactDOM.render()方法执行与服务端生成的字符挨个比对。如果一旦有不匹配的,不论什么原因,替换整个服务端的节点数。这样使得性能损耗很大。

16开始的检查变宽松了,他只替换他认为不一致的地方,单独替换。仅仅尝试修改不匹配的html子树,而不是修改整个HTML树。

src/components/App.js

import React, { Component } from 'react' export default class App extends Component { render() { return ( <div> <h1>Hello React</h1> </div> ) } }

server.js

... import React from 'react' import { renderToString } from 'react-dom/server' app.get('/', (req, res) => { const html = renderToString(<App />) res.send(html) // 这里返回的是组件转成的html,而不是React组件 }) ...

这样就能看到Hello React了

  • React同构

如果在上面的App中写个button并绑定onClick,会发现这并不会有什么作用。服务器端渲染是不能做行为的,行为和交互这些的需要客户端处理。

所谓React同构就是客户端代码和服务端代码保持一致。用create-react-app生成的项目build后,将renderToString出的html嵌入到build目录下的index.html的root下

const html = fs.readFileSync('./build/index.html') const content = renderToString(<App list={list}/>) res.send(html.toString().replace('<div id="root"></div>', `<div id="root">${content}</div>`))

将build的文件夹通过

app.use('/', express.static('build'))

返回给客户端。这样之前的onClick就能用了。查看浏览器查看DOM元素就能看到下面有script标签了。里面的js会去检查root下的代码跟它要生成的DOM结构是不是一致,如果一致就不管了,否则覆盖root下DOM,因此要保持两端的代码一致。因此,如果此时改了App.js,但是没有重新build,会发现页面不会改变,就是因为客户端发现DOM结构跟他加载的js代码要渲染的结构不一样,然后给覆盖了。

而Next.js帮我们处理了这些问题。