nodejs要加载es6语法,比如import export
,要加载react标签,那就需要babel
先把依赖装好
"devDependencies": {
"@babel/core": "^7.6.4",
"@babel/node": "^7.6.3",
"@babel/preset-env": "^7.6.3",
"@babel/preset-react": "^7.6.3",
"@babel/register": "^7.6.2"
},
"dependencies": {
"express": "^4.17.1",
"react": "^16.10.2",
"react-dom": "^16.10.2"
}
把.babelrc整出来
{
"presets":[
"@babel/preset-env",
"@babel/preset-react"
]
}
创建index.js
require( "@babel/register" )
require( "./src/server.js" )
启动刚才的index.js
node index.js
server.js
文件还没写,先空着,这样就可以用node加载含有es6
和react
的js代码了。
先创建server.js
import express from "express";
import path from "path";
import React from "react";
import { renderToString } from "react-dom/server";
import Layout from "./components/Layout";
const app = express();
app.use( express.static( path.resolve( __dirname, "../dist" ) ) );
app.get( "/*", ( req, res ) => {
const jsx = ( <Layout /> );
const reactDom = renderToString( jsx );
res.writeHead( 200, { "Content-Type": "text/html" } );
res.end( htmlTemplate( reactDom ) );
} );
app.listen( 2048 );
console.log('listen( 2048 )')
function htmlTemplate( reactDom ) {
return `
React SSR
${ reactDom }
`;
}
然后是Layout.js
import React from "react";
class Layout extends React.Component {
constructor() {
super();
this.state = {
title: "Welcome to React SSR!",
};
}
render() {
return (
<div>
<h1>{ this.state.title }</h1>
</div>
);
}
}
export default Layout;
需要继续装依赖,react、react-dom,缺什么装什么,
然后运行node index.js
,打开浏览器,访问2048端口,
就能看到页面了
这只是html页面而已,底部script标签中的app.bundle.js
还没生成,
app.bundle.js
就是浏览器端页面
src/client.js
import React from "react";
import ReactDOM from "react-dom";
import Layout from "./components/Layout";
const app = document.getElementById( "app" );
ReactDOM.hydrate( <Layout />, app );
然后webpack打包,放到dist里面,这就是app.bundle.js
webpack打包的代码就不贴了
不过注意,浏览器端和服务端要用一样的Babel插件
1.服务端和浏览器端渲染的是同一个react应用Layout
,
2.服务端用renderToString
把
变成字符串放进id为app的div中,
3.相对应的是,浏览器端用ReactDOM.hydrate
方法把
绑定在同一个div上
最后,app.bundle.js加载之后,react会加上event handlers
这里开始感觉有坡度了,至少之前我是这样。
答:
router可以通过url来访问不同的页面,
比如:
http://localhost:2048/about
http://localhost:2048/contact
这两个访问的页面视图就是不同的,
ssr要做到的就是,
无论我们是从哪个链接进入,那个页面都是服务端已经渲染好的
给Layout加上多个视图,
加上
标签,
import { Link, Switch, Route } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Contact from "./Contact";
export default class Layout extends React.Component {
/* ... */
render() {
return (
<div>
<h1>{ this.state.title }</h1>
<div>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</div>
<Switch>
<Route path="/" exact component={ Home } />
<Route path="/about" exact component={ About } />
<Route path="/contact" exact component={ Contact } />
</Switch>
</div>
);
}
}
这个文件是服务端和浏览器端共享的,
接下来修改两端的渲染代码:
server.js中把我们的react应用放在StaticRouter中
//server.js
/* ... */
import { StaticRouter } from "react-router-dom";
/* ... */
app.get( "/*", ( req, res ) => {
const context = { };
const jsx = (
<StaticRouter context={ context } location={ req.url }>
<Layout />
</StaticRouter>
);
const reactDom = renderToString( jsx );
res.writeHead( 200, { "Content-Type": "text/html" } );
res.end( htmlTemplate( reactDom ) );
} );
/* ... */
client.js中把我们的react应用放在BrowserRouter中
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import Layout from "./components/Layout";
const jsx = (
<Router>
<Layout />
</Router>
);
const app = document.getElementById( "app" );
ReactDOM.hydrate( jsx, app );