其實前面的SSR是不完整的,平時在開發的過程當中不免會有一些事件綁定,好比加一個button:html
// containers/Home.js
import React from 'react';
const Home = () => {
return (
<div> <div>This is sanyuan</div> <button onClick={() => {alert('666')}}>click</button> </div>
)
}
export default Home
複製代碼
再試一下,你會驚奇的發現,事件綁定無效!那這是爲何呢?緣由很簡單,react-dom/server下的renderToString並無作事件相關的處理,所以返回給瀏覽器的內容不會有事件綁定。前端
那怎麼解決這個問題呢?node
這就須要進行同構了。所謂同構,通俗的講,就是一套React代碼在服務器上運行一遍,到達瀏覽器又運行一遍。服務端渲染完成頁面結構,瀏覽器端渲染完成事件綁定。react
那如何進行瀏覽器端的事件綁定呢?webpack
惟一的方式就是讓瀏覽器去拉取JS文件執行,讓JS代碼來控制。因而服務端返回的代碼變成了這樣:web
那麼這個index.js咱們如何生產出來呢?express
在這裏,要用到react-dom。具體作法其實就很簡單了:npm
//client/index. js
import React from 'react';
import ReactDom from 'react-dom';
import Home from '../containers/Home';
ReactDom.hydrate(<Home />, document.getElementById('root')) 複製代碼
而後用webpack將其編譯打包成index.js:json
//webpack.client.js
const path = require('path');
const merge = require('webpack-merge');
const config = require('./webpack.base');
const clientConfig = {
mode: 'development',
entry: './src/client/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'public')
},
}
module.exports = merge(config, clientConfig);
//webpack.base.js
module.exports = {
module: {
rules: [{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react', ['@babel/preset-env', {
targets: {
browsers: ['last 2 versions']
}
}]]
}
}]
}
}
//package.json的script部分
"scripts": {
"dev": "npm-run-all --parallel dev:**",
"dev:start": "nodemon --watch build --exec node \"./build/bundle.js\"",
"dev:build:server": "webpack --config webpack.server.js --watch",
"dev:build:client": "webpack --config webpack.client.js --watch"
},
複製代碼
在這裏須要開啓express的靜態文件服務:瀏覽器
const app = express();
app.use(express.static('public'));
複製代碼
如今前端的script就能拿到控制瀏覽器的JS代碼啦。
綁定事件完成!
如今來初步總結一下同構代碼執行的流程:
如今寫一個路由的配置文件:
// Routes.js
import React from 'react';
import {Route} from 'react-router-dom'
import Home from './containers/Home';
import Login from './containers/Login'
export default (
<div> <Route path='/' exact component={Home}></Route> <Route path='/login' exact component={Login}></Route> </div>
)
複製代碼
在客戶端的控制代碼,也就是上面寫過的client/index.js中,要作相應的更改:
import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter } from 'react-router-dom'
import Routes from '../Routes'
const App = () => {
return (
<BrowserRouter> {Routes} </BrowserRouter>
)
}
ReactDom.hydrate(<App />, document.getElementById('root')) 複製代碼
這時候控制檯會報錯,
// server/index.js
import express from 'express';
import {render} from './utils';
const app = express();
app.use(express.static('public'));
//注意這裏要換成*來匹配
app.get('*', function (req, res) {
res.send(render(req));
});
app.listen(3001, () => {
console.log('listen:3001')
});
複製代碼
// server/utils.js
import Routes from '../Routes'
import { renderToString } from 'react-dom/server';
//重要是要用到StaticRouter
import { StaticRouter } from 'react-router-dom';
import React from 'react'
export const render = (req) => {
//構建服務端的路由
const content = renderToString(
<StaticRouter location={req.path} > {Routes} </StaticRouter>
);
return ` <html> <head> <title>ssr</title> </head> <body> <div id="root">${content}</div> <script src="/index.js"></script> </body> </html> `
}
複製代碼
如今路由的跳轉就沒有任何問題啦。 注意,這裏僅僅是一級路由的跳轉,多級路由的渲染在以後的系列中會用react-router-config中renderRoutes來處理。