那怎么解决这个问题呢?
这就需要进行同构了 。所谓同构,通俗的讲,就是一套React代码在服务器上运行一遍,到达浏览器又运行一遍 。服务端渲染完成页面结构,浏览器端渲染完成事件绑定 。
那如何进行浏览器端的事件绑定呢?
唯一的方式就是让浏览器去拉取JS文件执行,让JS代码来控制 。于是服务端返回的代码变成了这样:
文章插图
有没有发现和之前的区别?区别就是多了一个script标签 。而它拉取的JS代码就是来完成同构的 。
那么这个index.js我们如何生产出来呢?
在这里,要用到react-dom 。具体做法其实就很简单了:
//client/index. jsimport React from 'react';import ReactDom from 'react-dom';import Home from '../containers/Home';ReactDom.hydrate(<Home />, document.getElementById('root'))复制代码
然后用webpack将其编译打包成index.js://webpack.client.jsconst 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.jsmodule.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.jsimport 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'))复制代码
这时候控制台会报错,文章插图
因为在Routes.js中,每个Route组件外面包裹着一层div,但服务端返回的代码中并没有这个div,所以报错 。如何去解决这个问题?需要将服务端的路由逻辑执行一遍 。
// server/index.jsimport 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.jsimport Routes from '../Routes'import { renderToString } from 'react-dom/server';//重要是要用到StaticRouterimport { 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=https://www.isolves.com/it/cxkf/bk/2020-05-12/"/index.js">