服务端渲染介绍:
优点:
1.更好的 SEO,利于搜索引擎抓取页面信息。
2.加快首屏渲染。
一般使用有两种情况:
1.请求接口返回html,服务端渲染完成某个单页面然后返回给前端。
2.前端项目放在node环境去渲染运行,相当于在node中开发项目。
准备工作
创建项目:
不使用ts可以去掉 --typescript
npx create-react-app ssr-demo --typescript
创建 node 代码主干:
创建两个js文件
app.js用来存放主要的node代码,新建start.js、.babelrc用于配置babel。
因为node不支持ES6,react等语法,我们需要引入babel来做配置:
npm i babel-cli babel-preset-env babel-preset-react babel-preset-stage-0
start.js
require('babel-polyfill');
require('babel-register');
require('./server/app.js');
.babelrc
{
"presets": [
"env",
"stage-0",
"react",
],
}
app.js
// import express from 'express';
const express = require('express');
const app = express();
const config = {
port: 8000,
}
app.get('/', (req, res) => {
res.send('hello~!');
})
app.listen(config.port, () => {
console.log(`启动: 127.0.0.1:${config.port}`);
})
配置 node 热更新:
node 的热更新需要安装两个插件:
Cross-env :跨平台配置环境变量
Nodemon: node的热更新
npm i cross-env nodemon
在 package.json 文件中添加下面语句。
现在整个开发环境就配置完成。
接口返回html
先试着把完整的html返回给前端,app.js里面的代码修改为:
const express = require('express');
const fs = require('fs');
const app = express();
const config = {
port: 8000,
}
app.get('/', (req, res) => {
// res.send('hello~!!');
var index = fs.readFileSync('./public/index.html');
var html = index.toString();
res.send(html);
})
app.listen(config.port, () => {
console.log(`启动: 127.0.0.1:${config.port}`);
})
再把public中index.html删改一下:
运行 npm run hot 看结果:
这个一个简单的返回html就完成了。
react组件渲染
最重要的部分来了! 在server目录中创建文件夹compontents并在里面创建demo.js文件:
demo.js 中创建组件 Demo:
import React from 'react';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
mytext: '默认1'
}
this.change = this.change.bind(this);
}
change(e) {
this.setState({
mytext: e.target.value
})
}
render() {
return (
<div className="App">
<h2>{this.state.mytext}</h2>
<input value={this.state.mytext} onChange={this.change}></input>
</div>
)
}
}
export default Demo;
在app.js中引入使用
react 和 react-dom/server,用于转换react的 Demo 组件:
const express = require('express');
const fs = require('fs');
const app = express();
const config = {
port: 8000,
}
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Demo from './compontents/demo';
app.get('/', (req, res) => {
var index = fs.readFileSync('./public/index.html');
// renderToString, renderToStaticMarkup
var demo = ReactDOMServer.renderToString(<Demo />);
var html = index.toString().replace('hello~!', demo);
res.send(html);
})
app.listen(config.port, () => {
console.log(`启动: 127.0.0.1:${config.port}`);
})
看结果:
组件已经被渲染出来。
重点 renderToString 和 renderToStaticMarkup
renderToString: 渲染的时候带有 data-reactid属性 或者 data-reactroot属性。(当组件间有传递参数时显示data-reactid,没有传递任何参数显示data-reactroot)
renderToStaticMarkup: 则没有 data-reactid/data-reactroot 属性,单纯的html,可以节省一点流量。
data-reactid/data-reactroot 有什么用呢?
在客户端中 react 识别到有 data-reactid 就不会渲染前端的代码覆盖, 如果没有 data-reactid 前端就会忽略服务端元素,直接覆盖。
绑定方法
明明 Demo组件 中我们有绑定 change事件 去改变 mytext参数,但是怎样输入都没有效果, 因为 服务端渲染 出来的就只是单纯的 html ,只是用来显示。
怎样去绑定方法、事件和样式?
这个就是为什么要把 node 代码放在 create-react-app 的原因了。
先把server中的 compontents 拷贝到 src目录下:
这里面有同构原理,所以开发的时候必须保证服务端中的组件和前端组件保持一致(server 中 compontents === src 中 compontents)。
修改App.tsx
import React from '../node_modules/@types/react';
import './App.css';
import Demo from './compontents/demo';
const App: React.FC = () => {
return (
<div>
<Demo/>
</div>
);
}
export default App;
执行 npm start 看效果:
在客户端运行 react 是有样式、有事件的,但是可以到客户端的 html 首次加载时没有渲染组件内容。
首次加载把组件渲染出来,在上面代码已经实现了,现在就要把样式和相关JS引入进来:
npm run build
修改 server 中 app.js
const express = require('express');
const fs = require('fs');
const app = express();
const config = {
port: 8000,
}
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Demo from './compontents/demo';
app.get('/', (req, res) => {
// 把public目录改为build目录
var index = fs.readFileSync('./build/index.html');
var demo = ReactDOMServer.renderToString(<Demo />);
var html = index.toString().replace('hello~!', demo);
res.send(html);
})
app.listen(config.port, () => {
console.log(`启动: 127.0.0.1:${config.port}`);
})
显示没有找到相关文件,因为还没有把build目录公开,把 build 目录录入服务器:
app.use('/', express.static('./build'))
app.js
const express = require('express');
const fs = require('fs');
const app = express();
const config = {
port: 8000,
}
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Demo from './compontents/demo';
app.get('/', (req, res) => {
var index = fs.readFileSync('./build/index.html');
var demo = ReactDOMServer.renderToString(<Demo />);
var html = index.toString().replace('hello~!', demo);
res.send(html);
})
app.use('/', express.static('./build'));
app.listen(config.port, () => {
console.log(`启动: 127.0.0.1:${config.port}`);
})
可以看到首次渲染的时候 Demo 组件已经存在html中,而且样式和事件都正常:
到这里为止就已经完成了整个服务端渲染~~