# 正文

前面几节我们实现了一个最基础的 react ssr,同时也初步接触了同构,双端渲染同一个组件,服务端直出 html 结构,浏览器端也能够实现组件事件的绑定。

然后又对应用骨架的开发体验做了一次重要的升级。

# 引出问题

虽然我们能展示页面并且执行事件,但我们还缺一个非常重要的能力。

那就是路由!

现在我们只有一个路由,属于服务端的根路由 /

我们无论怎么在浏览器内改变路由地址都会显示同一个UI

当然我们的项目不可能只有一个页面,那我们该怎样来处理和维护项目的路由呢?

# 路由同构

由于我们打造的是基于服务端渲染的React SPA应用开发骨架,所以服务端和客户端都需要对路由进行处理。

我们使用的是React,那前端路由肯定会使用react-router来处理。

那服务端呢?服务端也需要单独维护一套路由?

当然不需要,这样不科学,更不合理。

在前几节我们初步接触了同构,现在解决这个问题的办法还是同构 - 路由同构,经过同构后服务端和客户端可以使用同一套路由。

# 同构思路与实现

先整体说下实现思路,让大家先有个基本的了解。

当第一请求页面的时候,服务端接收请求,根据当前的path来查找具体的路由,然后根据路由得到具体的组件,然后将组件直出。

服务端直出后,页面由浏览器接管,后面的渲染执行就交给前端代码了。

思路很简单,接下来看下具体的实现和代码。

创建一个客户端路由配置

react router4开始,react对路由做了重大的升级,将组件化的思想贯彻到底 - 一切皆组件。

所以从v4版本开始不再是集中式路由配置,路由也是组件,也可以和 UI 写在一起。

当然你仍然可以使用集中式的路由配置方式。

新增一个Layout组件作为页面公共组件,在这个组件内进行路由渲染,当然也可以不用,这里也是为了让每个页面都有公共部分,无需每重复渲染。

具体用不用看自身的业务即可。

// ./src/client/app/layout.js

import React from 'react';
import { Link } from 'react-router-dom';
export default class Index extends React.Component{
constructor(props){
 super(props);
}
render(){
return  <div>
    <Link to="/index">首页</Link>  <Link to="/artice">列表页</Link>
    <div>{this.props.children}</div>
    </div>
}
}

配置路由

顺便简单介绍几个react router4的路由组件 Route, Switch, BrowserRouter

Route 组件

用于绑定组件和path的关系,一般使用component属性指定要渲染的组件,其中exact属性表示是否是精确匹配模式,默认是false

 <Route path="/index"  exact={true} component={Index}></Route>
Switch 组件

使用该组件只会渲染第一个匹配到的路由,否则所有的路由都会渲染。

  <Switch>
      <Route path="/"  exact={true} component={Root}></Route>
      <Route path="/list" exact={true} component={List}></Route>
  </Switch>
BrowserRouter 组件

此组件相信大家都熟悉,基于浏览器 History api 来达到浏览器地址和 UI 同步的能力。

<BrowserRouter>
    <Switch>
        <Route path="/root"  exact={true} component={Root}></Route>
        <Route path="/list" exact={true} component={List}></Route>
    </Switch>
</BrowserRouter>

来看下完整的路由配置

提取为独立的模块,方便维护和管理。

// ./src/client/router/route-config.js
//路由配置文件

import Index from '../pages/index';
import List from '../pages/list';

export default [
    {
        path:'/index',
        component:Index,
        exact: true //是否精确匹配
    },
    {
        path: '/list',
        component: List,
        exact: true,
    }
]

路由渲染入口配置

遍历路由配置

// src/client/router/indxex.js
//路由配置文件

import Layout from '../app/layout';
import React  from 'react';
import { Route, Switch } from 'react-router-dom';

//服务端也会用到所以通过参数的方式将配置传递进来
function App({routeList}) {
    return (
        <Layout> //公共组件
            <Switch>
                {
                    routeList.map(item=>{
                            return <Route key={item.path} {...item}></Route>
                    })
                }
            </Switch>
        </Layout>
    );
}

export default App;

调整客户端组件渲染的入口代码

//client/app/index.js
//浏览器端页面结构渲染入口

import React from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter} from 'react-router-dom';
import App from '../router/index';
import routeList from '../router/route-config';//路由配置


//渲染入口
ReactDom.hydrate(
   - <Index>
   + <BrowserRouter>
        <App routeList={routeList} />
   </BrowserRouter>//改成路由组件
, document.getElementById('root'))

到这里,客户端路由已生效,路由改变同时能够渲对应的组件

image-20210214214106281

现在客户端路由基本上已经配置好,服务端该如何处理?

服务端路由处理

按照我们上面的思路,根据请求的 path,去路由配置里查找对应的组件,得到匹配的组件后,服务端完成组件直出。

上面的思路没什么问题,不过v4中 已经为我们提供了相关的组件来完成服务端的渲染。

StaticRouter
import { StaticRouter} from 'react-router';

该组件主要用于服务端渲染,可以帮助我们完成路由查找功能,无需再做手动匹配。

基本的思路是,将替换为无状态的。

将服务器上接收到的path传递给此组件用来匹配,同时支持传入context特性,此组件会自动匹配到目标组件进行渲染。

context属性是一个普通的JavaScript对象。

在组件渲染时,可向该对象添加属性以存储有关渲染的信息,比如302 404等结果状态,然后服务端可以针对不同的状态进行具体的响应处理。

对比来看

//客户端
<BrowserRouter>
      <App/>
</BrowserRouter>

// 服务端
<StaticRouter location={req.url} context={context}>
        <App/>
</StaticRouter>

服务端渲染处理

//引入客户端路由组件
//...
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter, Route} from 'react-router';
import App from '../../client/router/index';
import routeList from '../../client/router/route-config';


export default  (ctx,next)=>{

    //获得请求的 path
    const path = ctx.request.path;

    //渲染组件为 html 字符串
    const html = renderToString(<StaticRouter location={path}>
          <App routeList={routeList}></App>
    </StaticRouter>);
    ctx.body=`<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>my react ssr</title>
</head>
<body>
    <div id="root">
       ${html}
    </div>
</body>
</html>
</body>
<script type="text/javascript"  src="index.js"></script>
`;

    return next();
}

到这里我们已经实现了基本的双端路的同构,是不是很简单呢。^_^

image-20210214214127569

# 小结

本节主要是了解和实现 路由同构,整体来说比较简单,不过这也只是小试牛刀哦,后面还会更精彩。

github.com/Bigerfe/koa… (opens new window)

阅读全文