其实前面的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来处理。