本期精读的文章是:React Router 进阶:嵌套路由,代码分割,转场动画等等。
懒得看文章?没关系,稍后会附上文章内容概述,同时,更希望能通过阅读这一期的精读,穿插着深入阅读原文。
1 引言
React Router4.0 出来之前,许多人都对其夸张的变化感到不适,但其实 4.0 说不定真的是一个非常正确的改动。
也许,说 4.0 不好的人,正是另一个消极版的小红点,希望这篇文章可以让大家意识到,React Router4.0 对大多数人真的很棒!
2 内容概要
React Router4.0 正式版发布了,生态也逐渐完善了起来,是时候推一波与其完美结合的实用工具了!
代码分割
通过 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 (Ant Motion 也很好用) 可以实现路由级别的动画:
代码语言: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
简单完成滚动条复位的功能:
<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:
@POST("/api/service")
async someAction() {}
服务端渲染
浏览器端,需要一个专属的入口文件,使用 BrowserRouter
与 location
对接:
<BrowserRouter>
<App />
</BrowserRouter>
服务器端,BrowserRouter
变成了 StaticRouter
:
renderToString(
<StaticRouter
location={req.url}
context={context}
>
<html>
<body>
<App />
</body>
</html>
</StaticRouter>
)
与浏览器不同的是,React Router 无法根据 location 自动判断当前所在页面,而需要你把 req.url
传给 StaticRouter
,后续的路由渲染逻辑双端都是通用的。
如果存在跳转 Redirect
,会通过 context.url
告诉你,所以后面会跟上跳转处理逻辑:
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