react服务端渲染ssr实践,一次一步 2019年10月

给nodejs配置babel

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加载含有es6react的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端口,
就能看到页面了
react服务端渲染ssr实践,一次一步 2019年10月_第1张图片这只是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

加上React Router

这里开始感觉有坡度了,至少之前我是这样。

先思考一个问题,ssr+router会怎么样?

答:
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 );

你可能感兴趣的:(前端)