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

到這裏爲止就已經完成了整個服務端渲染~~

相關文章
相關標籤/搜索