本期精读的文章是：[React Router 进阶：嵌套路由，代码分割，转场动画等等](https://blog.logrocket.com/advanced-react-router-concepts-code-splitting-animated-transitions-scroll-restoration-recursive-17096c0cf9db)。

懒得看文章？没关系，稍后会附上文章内容概述，同时，更希望能通过阅读这一期的精读，穿插着深入阅读原文。

## 1 引言

<img src="https://img.alicdn.com/imgextra/i1/O1CN01dqFFbE1eDqlwYtZay_!!6000000003838-2-tps-698-312.png" alt="logo" width="500" />

React Router4.0 出来之前，许多人都对其夸张的变化感到不适，但其实 4.0 说不定真的是一个非常正确的改动。

![1.jpeg](https://img.alicdn.com/imgextra/i3/O1CN01JkJldr27J05KNogqx_!!6000000007775-2-tps-440-900.png)

也许，说 4.0 不好的人，正是另一个消极版的小红点，希望这篇文章可以让大家意识到，React Router4.0 对大多数人真的很棒！

## 2 内容概要

React Router4.0 正式版发布了，生态也逐渐完善了起来，是时候推一波与其完美结合的实用工具了！

### 代码分割

通过 [react-loadable](https://github.com/thejameskyle/react-loadable)，可以做到路由级别动态加载，或者更细粒度的模块级别动态加载：

```javascript
const AsyncHome = Loadable({
    loader: () => import('../components/Home/Home'),
    loading: LoadingPage
})

<Route exact path="/" component={AsyncHome} />
```

当然上面展示的是 ReactRouter 中的用法，`AsyncHome` 可以在任何 JSX 中引用，这样就提升到了模块级别的动态加载。

> 注意，无论是 webpack 的 Tree Shaking，还是动态加载，都只能以 Commonjs 的源码为分析目标，对 node_modules 中代码不起作用，所以 npm 包请先做好拆包。或者类似 antd 按照约定书写组件，并提供一种 webpack-loader 自动完成按需加载。

### 转场动画

通过 [React Router Transition](https://github.com/maisano/react-router-transition) ([Ant Motion](https://motion.ant.design/) 也很好用) 可以实现路由级别的动画：

```javascript
<Router>
  <AnimatedSwitch
    atEnter={{ opacity: 0 }}
    atLeave={{ opacity: 0 }}
    atActive={{ opacity: 1 }}
    className="switch-wrapper"
  >
    <Route exact path="/" component={Home} />
    <Route path="/about/" component={About}/>
    <Route path="/etc/" component={Etc}/>
  </AnimatedSwitch>
</Router>
```

并提供了一些生命周期的回调，具体可以参考文档。现在动画的思路比较靠谱的也大致是这种：通过添加/移除 class 的方式，利用 css3 做动效。

### 滚动条复位

当页面回退时，将滚动条恢复到页面最顶部，可以让单页路由看起来更加正常。由于 React Router4.0 中，路由是一种组件，我们可以利用 `componentDidUpdate` 简单完成滚动条复位的功能：

```javascript
<Router history={history}>
  <ScrollToTop>
    <div>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="*" component={NotFound} />
      </Switch>
    </div>
  </ScrollToTop>
</Router>
```

```javascript
@withRouter
class ScrollToTop extends Component {
    componentDidUpdate(prevProps) {
        if (this.props.location !== prevProps.location) {
            window.scrollTo(0, 0)
        }
    }

    render() {
        return this.props.children
    }
}
```

非通过 `Route` 渲染的组件，可以通过 `withRouter` 拿到路由信息，仅当其为 `Router` 的子元素时有效。

### 嵌套路由

React Router4.0 嵌套路由与 3.0 不同，是通过组件 `Route` 的嵌套实现的。

在任何组件，都可以使用如下代码实现嵌套路由：

```javascript
<Route path={`${this.props.match.url}/:id`} component={NestComponent} />
```

这样将路由功能切分到各个组件中，我现在的项目甚至已经没有 `route.js` 文件了，路由由 `layout` 与各个组件自身承担。这种设计思路与 `Nestjs` 的描述性路由具有相同的思想 - 在 nodejs 中，我们可以通过装饰器，在任意一个 `Action` 上描述其访问的 URL:

```javascript
@POST("/api/service")
async someAction() {}
```

### 服务端渲染

浏览器端，需要一个专属的入口文件，使用 `BrowserRouter` 与 `location` 对接：

```javascript
<BrowserRouter>
  <App />
</BrowserRouter>
```

服务器端，`BrowserRouter` 变成了 `StaticRouter`：

```javascript
renderToString(
  <StaticRouter
    location={req.url}
    context={context}
  >
  <html>
    <body>
      <App />
    </body>
  </html>
  </StaticRouter>
)
```

与浏览器不同的是，React Router 无法根据 location 自动判断当前所在页面，而需要你把 `req.url` 传给 `StaticRouter`，后续的路由渲染逻辑双端都是通用的。

如果存在跳转 `Redirect`，会通过 `context.url` 告诉你，所以后面会跟上跳转处理逻辑：

```javascript
if (context.url) {
  res.writeHead(301, {
    Location: context.url
  })
  res.end()
} else {
  res.write(markup)
  res.end()
}
```

## 3 精读

React Router 从 3.0 到 4.0 的改动，想来想去，认为是对于 URL 这个资源理解的变化。

URL 即浏览器地址，在前端数据化统一的浪潮下，其实 URL 也可以被看作是一种参数，在 React 中即一个 `props` 属性。

单页应用，如果从传统多页应用角度来思考，可能认为不过是一种体验的优化，或者是一种 “伪单页”，毕竟本质上单页应用只是一个页面而已。但换个角度想想，网站何尝不是一个整体，而网址的变化只是一种状态呢？

当我们做一个 Tabs 组件时，会发觉做得越来越像浏览器原生 tab，当用户给你提需求，在刷新浏览器时，能自动打开上一次打开的 Tab，我们的做法就是将当前打开的 Tab 信息保存在 URL 中，刷新时读取再切换过去。这证明了 URL 表示的就是一种状态。

而页面路由的状态化，是将模拟 Tab 的思路应用到了浏览器级别的 Tab。URL 是一种状态，在前端，可以通过浏览器地址自动获取，在后端，可以通过 `req.url` 获取，甚至可以手动传入来覆盖。

传统的开发思路：我们为每个 URL 编写独立的页面或者模块。

**新的开发思路：URL 是一个状态，代码读取这个状态作出不同展现，展现得完全不同时，可以看作传统模式的页面切换；但还可以做到只有某一块区域展现得不同。**

## 4. 总结

也许 React Router4.0 带给我们的思考是，放下对网页“页面”的刻板印象，其实网站本没有页面，有的只是状态。

> 讨论地址是：[精读《React Router4.0 进阶概念》》 · Issue #43 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/43)

**如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。**