Tags: JavaScript, React
引子
本文会讨论react生态下的常用路由库,React-router的版本迭代与源码架构,并尝试探讨路由思维的变化与未来。
什么是路由?
路由是一种向用户显示不同页面的能力。 这意味着用户可以通过输入URL或单击页面元素在WEB应用的不同部分之间切换。
版本
为了探究react-router设计思维,从v3开始有这几个版本:
react-router 3「静态路由」
react-router 4「动态路由」
react-router 5「意外发布」
@reach/router「简化轻量」
react-router 6「完全方案」
让我们逐个参与讨论。
react-router3:静态路由
静态路由的设计如下图所示:
React.render((
<Router>
<Route path="/" component={Wrap}>
<Route path="a" component={App} />
<Route path="b" component={Button} />
</Route>
</Router>
), document.body)
特点:
路由集中在外层
页面路由配置通过
Route
组件的嵌套而来布局和页面组件是完全纯粹的,它们是路由的一部分
v3静态路由的设计对前端工程师来说,相对更易接受,因为前端工程师很多都接触过类似的路由配置设计,比如express、rails等框架。
虽然细节各有不同,但是思路大致相同——将path静态映射为渲染模块。
react-router4:动态路由
虽然v3以一种质朴无华的方式完成了基本的路由工作,但react-router的几个核心成员感觉现有的实现严重受ReactAPI的制约,并且实现方式也不够优雅。
于是,经过了激烈的思考与讨论,他们大胆地在v4中做出了比较激进的更迭。
React-router4不再提倡静态路由的集中化架构,取之的是路由存在于布局和 UI 之间:
const App = () => (
<BrowserRouter>
<div>
<Route path="/a" component={A}/>
</div>
</BrowserRouter>
);
const A = ({ match }) => (
<div>
<span>A</span>
<Route
path={match.url + '/b'}
component={B}
/>
</div>
);
const B = () => <div>B</div>;
我们来看以上代码的逻辑
一开始在App组件里,只有一个路由
/a
用户跳转访问
/a
时,渲染A
组件,浏览器上出现字母A,然后子路由/b
被定义用户跳转访问
/a/b
时,渲染B
组件,浏览器上出现字母B
我们可以看到,在v4中:
路由不再集中在一处
布局和页面的层叠不再由层叠的
<Route>
组件控制,<Route>
与组件为替换的关系布局和页面组件也不在是路由的一部分
这被称之为「动态路由」。
动态路由
传统静态路是在程序渲染前就定义好。
而动态意味着路由功能在应用渲染时才动态生成,这需要把路由看成普通的 React 组件,传递 props
来正常使用,借助它来控制组件的展现。这样,没有了静态配置的路由规则,取而代之的是程序在运行渲染过程中动态控制的展现。
动态路由将带来很大的好处。比如代码分割,也就是react常说的code splitting
,由于不需要在渲染前决定结果,动态路由可以满足代码块的按需加载,这对于大型在线应用非常有帮助。
但是,毕竟路由对一个应用的架构来说非常重要,这么大的改变显得过于激进,这会改变以前开发者比较习惯的一些模式,由于这次的更新过于激进,遭到了开发者们的一些负面反馈:
这就要讨论到动态路由的缺点了:
不够直观,你无法从顶层知道程序中所有的路由,应用一层一层下来,搞不清最后显示出来什么,可读性很差
测试困难。组件中掺杂了路由逻辑,原本对针对组件的单元测试(功能层面)完全不需要知道路由的存在,而现在就要考虑了
由于React-router团队保证v3会持续维护,所以当时很多开发者没有选择升级。
react-router5:沿用
原本只是计划发布 React Router 4.4 版本,但由于不小心误用了^
字符,将依赖错误地写成 "react-router": "^4.3.1"
,导致报错。于是最后团队决定撤销 4.4 版本,直接改为发布 React Router v5。
react-router5延续了动态路由的模式,但是提供了更加直观的写法:
export default function App() {
return (
<Router>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/topics">
<Topics />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
以上的写法,/about
显示<About>
组件,/topics
显示<Topic>
组件,根路由显示<Home>
组件。
同时,v5还允许你将路由配置作为一个config的json数据,写在组件外引入。
<Route>
将作为父组件用于匹配路由,同时还有一系列辅助组件,比如<Switch>
可以限制子元素进行单一的路由匹配。当然,这也会带来一定的
@reach/router:简洁
Reach-Router 是前 ReactRouter 成员 Ryan Florence 开发的一套基于 react 的路由控件。
那么已经有比较成熟的 ReactRouter 了, 为什么要”再”做一套 Router 呢?
Accessibility「易用」
相对链接的跳转方式
嵌套的路由配置
合适的路径优先(顺序不会造成影响)等等
优点:小而简
4kb,压缩后比
react-router
小40kb左右,同时有更少的配置比起react-router需要3个包(
history
,react-router-dom
,react-router-redux
),reach-router
只需要一个不需要在
store
配置router
相关信息不需要显示的使用
history
基本一样的api,学习成本非常低
源码非常简洁,总共就3个文件,900行
react-router6:终极方案
2021年11月,react-router 6.0.0 正式版发布:
全部用 ts 重写
不以 '/' 开头,都是「相对路径」
路由按照最佳匹配选择,可以嵌套或者分散
v6的设计可以说很大程度参照了@reach/router,API和@reach/router v1.3非常相似。因此,官方也宣称v6可以被看做@reach/router的v2。
总体来说,v6更像是一个以前版本的完善和整合,相对路径与嵌套分散的选择方式,让大家能够按个人喜好去构建路由。
源码
探讨完设计哲学与版本更迭,我们正式进入从0到1的源码学习。
本文对源码的探讨,就是以v6为基础(中间存在各种简