Node + React : 服务端渲染SSR

服务端渲染介绍:

优势:javascript

1.更好的 SEO,利于搜索引擎抓取页面信息。css

2.加快首屏渲染。html

通常使用有两种状况:前端

1.请求接口返回html,服务端渲染完成某个单页面而后返回给前端。java

2.前端项目放在node环境去渲染运行,至关于在node中开发项目。node

准备工做

建立项目:

不使用ts能够去掉 --typescriptreact

npx create-react-app ssr-demo --typescript
复制代码

建立 node 代码主干:

建立两个js文件typescript

app.js用来存放主要的node代码,新建start.js、.babelrc用于配置babel。express

由于node不支持ES6,react等语法,咱们须要引入babel来作配置:npm

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中,并且样式和事件都正常:

到这里为止就已经完成了整个服务端渲染~~

相关文章
相关标签/搜索