react服务器端渲染原理解析和实现(二)——了解:事件绑定和路由渲染

我绑定的事件哪去了?

  在我的上一个博客中,我们实现了最基本的SSR服务端渲染的流程,学习了组件的服务端渲染方式,那就是通过renderToString方法将其渲染为字符串,但是我也说了,由于被渲染成了字符串,所以一系列的事件绑定都没有应用上去。
  接下来我们就来学习事件的绑定。
  我们都知道SSR有两套代码,一套在服务端运行一次,一套在客户端运行一次,所以我们这里需要改动客户端那一套的代码。改造起来特别简单,就是将你写的react代码在客户端再写一次(由于我们在编写配置文件的时候已经将客户端的配置文件编写了,所以我们只需要将客户端的文件书写出来)。有些同学就可能要问:这不是很麻烦吗?其实一点都不麻烦,代码如下:

import React from 'react'
import {hydrate} from 'react-dom'
import Home from '../pages/Home'

const App = () => {
    return (
        <Fragment>
			<Home />
		</Fragment>
    )
};

hydrate(<App />, document.getElementById('#root'));

  这里我们使用hydrate(专业词汇:注水)代替render,有两个原因:

  • 使用render会报错:
    因为我们再服务端渲染的时候用了render,在客户端再次运行render函数就会出现重复的情况。
  • hydrate复用:
    hydrate会尽量复用接收到的服务端返回的内容,来补充事件绑定和浏览器端其他特有的过程。

  按照服务端渲染的流程,当浏览器解析完成之后,也一样会解析客户端的那一套js文件,但是我们并不是像原来客户端渲染那样慢,因为浏览器现在呈现给我们的就是一个完整的网页(虽然是以字符串的形式响应的),首屏加载很快,这样浏览器就有空余的事件去解析客户端的react代码。
  这样,我们就可以在Home组件里面绑定事件了,而且浏览器也可以成功的响应事件。

难道就能存在一个页面吗?

当然不是,这里我们就会学习到react中路由的渲染,首先我们来回忆一下客户端渲染的路由。

  • BrowserRouter/HashRouter
    客户端渲染使用的Router是BrowserRouterHashRouter
    BrowserRouter是以服务器的形式对浏览器BOM对象中的history进行修改;而HashRouter则是使用锚点的方式实现路由的功能。

那么我们再来探讨一下服务器渲染的Router

  • StaticRouter
    在服务端渲染的时候,我们就不能使用BrowserRouter/HashRouter了,我们使用全新的一个Router——StaticRouter,相信很少有伙伴使用它吧。而我们服务端的路由渲染就要靠他了。

可能有很多同学没注意到,我们在写服务端express的请求时候的请求路径是 “ * ”,记住必须要把请求路径设置为*

  • 我们不可能每有一个页面就要去配置一个路由再渲染一次
  • 路由跳转的时候也不会也必须用到req

怎么配置呢?

这里我使用更加规范的写法,还请各位理解清楚。
为了比较使用路由我在pages下面新建了一个Login组件

  • 新建route文件,作为我们的路由配置
import Home from '../pages/Home'
import Login from '../pages/Login'

export const mainRoute = [
    {
        path: '/home',
        component: Home,
        exact: true
    },
    {
        path: '/login',
        component: Login
    }
];
  • 新建Login组件
import React, {Component, Fragment} from 'react'
import Header from '../../components/Header' // 引入了Header组件
export default class Login extends Component {
    render() {
        return (
            <Fragment>
                <Header/>
                this is login
            </Fragment>
        )
    }
}
  • 新建Header组件
import React, {Fragment} from 'react'
import {Link} from 'react-router-dom'

export default () => {
    return (
        <Fragment>
            <Link to='/'>Home</Link><br />
            <Link to='/login'>Login</Link><br />
        </Fragment>
    )
};
  • 在服务端、和客户端渲染路由
    • src目录下新建config文件夹,在里面新建route.js
      route.js就是我们渲染客户端和服务器端的共同配置,望大家仔细阅读一下代码
    import React from 'react'
    import {mainRoute} from "../route";
    import {Provider} from 'react-redux'
    import {BrowserRouter, StaticRouter, Switch, Redirect, Route} from "react-router-dom";
    
    export const Routes = (props) => {
    	// 这里通过判断客户端和服务端传过来的参数进行location和context的指定渲染
    	// 如果传过来的router是BrowserRouter那就使用BrowserRouter
        let Router = props.router === 'BrowserRouter' ? BrowserRouter : StaticRouter;
        let location = props.req ? props.req.url : '', context = props.context ? props.context : '';
        console.log(props);
        return (
                <Router location={location} context={context}>
                {/* StaticRouter需要两个参数
                location(固定参数:req.url,表示客户端请求服务器端的地址,
                因为服务器端不是浏览器,所以需要传递req.url供服务端识别)
                context(服务端下需要接受的参数,可以自己定义) */}
                    <Switch>
                        {
                            mainRoute.map((item, index) => {
                                return (
                                    <Route exact={item.exact} key={index} path={item.path}
                                           render={props => <item.component {...props}/>}/>
                                )
                            })
                        }
                        <Redirect from='/' to='/home'/>
                    </Switch>
                </Router>
        );
    };
    
    由于我们在mainRoute里面配置了路由所需要的参数,所以我们在这里直接通过map进行渲染,这个方法也适用与平时我们些项目的时候,我推荐这样写,因为层次结构分明,而且项目越大,这种书写方式优势越大。
    • 服务端引入
      修改服务端代码,将冗长的html部分抽离出来
      • 在服务端文件同级新建utils.js,并修改服务器端文件
    import React from 'react'
    import {renderToString} from "react-dom/server";
    import {Routes} from '../config/route'
    
    export const render = (req) => {
        const content = renderToString( // 渲染成字符串
            <Routes router='StaticRouter' req={req} context={{}}/>
        );
        return `
            <!doctype html>
            <html lang="en">
                <head>
                    <title>hello</title>
                    <body>
                   <div id="root">${content}</div>
                   <script src="/index.js"></script><!-- 引入打包之后的客户端文件 -->
                    </body>
                </head>
            </html>`
    };
    /*-------------------服务器端修改文件代码----------------*/
    import {render} from './utils'
    
    app.use(express.static('public')); // 使用中间件,将public设置成静态文件,
    									//这个文件是打包之后的客户端文件
    
    app.get('*', (req, res) => {
    	 res.send(render(req))
    });
    
    • 客户端引入,修改客户端代码
    import {Routes} from '../config/route'
    
    const App = () => {
        return (
            <Routes router='BrowserRouter'/>
        )
    };
    

文件目录这样子
在这里插入图片描述
这样的话,路由的服务端渲染就完成了。也请大家在平时写项目的时候使用结构化较强的目录,公共的重复的拆分出来。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值