乐趣区

[ 一起学React系列 — 11 ] React-Router4 (1)

2019 年不知不觉已经过去 19 天了,有没有给自己做个总结?有没有给明年做个计划?当然笔者已经做好了明年的工作、学习计划;同时也包括今年剩下的博文计划,在这里透露下:年前 (对,没有写错,是年前) 完成该系列博客,目前还剩 4 篇:分别是两篇 React-Router4 和两篇 immutability-helper。本篇是 React-Router4 的第一篇,在正式开始之前大家可以先看下实际效果,这是笔者用 React-Router4 写的例子。
React-Router4
实际上,笔者接触的第一个 React 中的路由模块就是 React-Router,只不过当时还是 v3 版本。因为没有太多深入得学习研究,所以在这里不会对 v4 之前的版本作介绍或者评价(其实并不代表笔者对 v4 版本有深入的研究学习,汗 …)。下面列举些 React-Router4 中需要知道的一些概念或者 emmmm… 小知识点。
React-Router4 中的包
React-Router4 中的包主要有三个 react-router、react-router-dom 和 react-router-native。据考证(手动斜眼笑),React-Router4 已经将前身切分了出来分别整合成了单独的 module。其实笔者一开始看到也挺懵逼的,这三个包到底是什么玩意?

react-router:“The core of React Router”。简单说就是逻辑代码。react-router-dom:“DOM bindings for React Router”。这个模块不仅仅包含了 react-router 的模块,还包含了页面渲染功能,也就是可以在页面上显示。react-router-native:“React Native bindings for React Router”。这个也很好理解,就是可以在 React-Native 中使用。
路由根节点
React-Router4 的理念是一切皆组件。React-router-dom 则提供了两个路由根节点:HashRouter 和 BrowserRouter。

HashRouter: 通过 hash 值来对路由进行控制,而且你会发现一个现象就是 url 中会有个 #,例如 localhost:3000/#。对于笔者这种有强迫症的人来说怎么能忍?所以笔者就没再碰这个组件。BrowserRouter: BrowserRouter 就相对舒服点。它的原理是使用 HTML5 history API (pushState, replaceState, popState)来使你的内容随着 url 动态改变的,而不会出现莫名其妙的 #。
React-Router4 的理念
上面提到:React-Router4 的理念是一切皆组件 (以下统一称“组件”)。我们 v4 之前的版本都需要在一个 js 文件中配置整个项目的路由信息然后在 App.js(以 create-react-app 脚手架创建的 React 项目为例) 引入并使用。而在 React-Router4 中则将所有的功能以 React 组件的形式提供出来,意思就是说我们可以像使用普通组件一样使用路由组件并最终在项目中形成一棵路由树。在这里笔者要注重说下,一个项目中尽量只有一棵路由树,除非有特殊需求,否则的话会造成一些奇怪的问题。通俗点说就是不要在一棵树上栽另一棵树。下面用一张图来简单展示下 React-Router4 的使用:

如图是 React-Router4 使用规则和最简单的使用实践。当然这种写法 (我称它叫组件型路由,下同) 和之前的配置型路由哪个更好用是青菜萝卜的问题,不需要太多纠结,自己喜欢就好。接下来的介绍都是笔者通过学习官方文档并做了一些实践后的心得。如有不当或者错误,欢迎指正。对于第一次学习该模块的童鞋,笔者建议将代码下载下来一遍看代码一遍看本文,也可以打开笔者的 demo,这样会更深刻。
常用路由组件介绍
下面这段代码是该 demo 根路由配置的代码(其实不存在什么根路由概念,只是笔者喜欢这么叫它)。从这里就能明显看出来 react-router3 和 4 两个版本的差异。react-router3 有自己独立的一个配置中心文件,而 react-router4 则把跳转的规则嵌入到实际代码中,不需要额外维护一个路由配置文件。从这点来说的确方便了不少,也迎合 React 一切皆组件的理念。
<Router>
<div className={AppStyle[“Container-body”]}>
<nav className={AppStyle[“App-nav-list”]}>
<ul>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/basic”> 基础路由 </NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/param”> 带参路由 </NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/nesting”> 嵌套路由 </NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/ambiguous”> 暧昧匹配 </NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/auth”> 权限路由 </NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to=”/404″>404</NavLink></li>
</ul>
</nav>
<div className={AppStyle.content}>
<Switch>
<Redirect exact from=”/” to=”/basic” />
<Route path=”/basic” component={renderBasicRouter} />

<Route path=”/param” component={RouterWithParameters} />
<Route path=”/ambiguous” component={renderAmbiguousRouter} />
<Route path=”/nesting” component={renderNestingRouter} />
<Route path=”/auth” component={authenticationRouter} />
<Route component={NoMatch} />
</Switch>
</div>
</div>
</Router>
从这段代码中需要了解的概念包括:Router、NavLink、Route、Redirect、exact、Switch。
Router
上面已经介绍过了,这里笔者用的是 BrowserRouter
NavLink
NavLink 可以触发路由的跳转,当然类似的组件还有 Link。它们都可以通过指定属性 to 的值来告诉 Router 我们要跳转到那个 Route,实际上 NavLink(Link)和 Route 本就已经通过 to 和 path 两个属性建立起关系了。而 NavLink 与 Link 的区别在于各自 API 的数量,因为 NavLink 可用的 API 相对较多,所以笔者更青睐 NavLink。具体 API 有兴趣的可以自行 Google,常用的 API 笔者已经在项目中使用。
Route
Route 组件是 React Router4 中主要的组成单位,可以认为是 NavLink 或 Link 的路由入口。在这里笔者要重点 Tip 一下,上一条说 NavLink 或 Link 可以触发路由的跳转,实际上它们的实现流程是这样的:
NavLink(Link)改变地址栏的 pathname,Router 会根据 pathname 去匹配它子组件中的 Route 中的 path 属性,一旦匹配上就会渲染该 Route 对应的组件 `。所以 NavLink(Link)与 Route 并没有并存的关系,因为 NavLink(Link)只是用来改变 pathname,不会直接去调用任何 API。所以当我们在地址栏直接手动输入路由,也会发生路由渲染。
具体匹配规则请参考 path-to-regexp,或者通过这个网站进行测试。
Redirect
Redirect 相当于转发器。Router 内部去匹配路由入口的时候也会去匹配 Redirect 的 from 属性值。一旦匹配到了 Redirect,Redirect 就会将这个跳转请求转向自己的 to 属性值对应的路由。所以这个过程可以这样理解:当我们访问页面路径的时候,比如:http://ip:3001/,就会捕获到 ’/’ 这个路由跳转请求,Router 开始在 Route 中匹配随后匹配到了 Redirect,Redirect 自行发起路由跳转请求 ’/basic’,Router 开始像往常一样在 Route 中匹配直到匹配到了 path 为 ”/basic” 的 Route,随后对 Route 对应的 component 进行渲染。
exact
exact 将该 Route 标示为严格匹配路由。什么叫严格匹配路由?就是 pathname 必须与 Route 的 path 属性值完全一致才算匹配上。例如:

pathname
path
匹配结果

/home
/home
可以匹配

/home/child
/home
无法匹配

这里 Tip 下:如果某个 Route 对应的组件中也有 Route,那么千万不要 在这个 Route 中加 exact,不然你会发现完全匹配不到子路由。切记,因为笔者最近刚踩过这个坑。所以这里笔者建议大家只在叶子 Route 中使用 exact,也就是最后一级 Route 中使用 exact,当然 exact ‘/’ 除外。
Switch
Switch 可以将多个 Route 包裹成一组 Route,当进行路由匹配时候,该组中的路由一旦发生匹配那么就不会匹配改组中剩下的路由。也就是说该组中的路由最多只会被匹配到一个。
Route 的 component 属性
追加一条。Route 的 component 属性对应的属性值通常是一个组件或者一个方法。而如果是方法的话,比如例子中:
const renderBasicRouter = ({match}) => <Basic url={match.url} path={match.path} />;
那么传入的参数为:
所以如果在跳转前有什么特殊逻辑需要处理,比如我们想让不同的页面有不同的前缀,比如例子中的 /basic/Home、/param/home 等,那么就可以如例子一样处理。但如果不需要特殊处理的话,直接把组件放到 component 属性下即可。当然 router 相关的参数也会通过 props 传给该组件。
这里 Tip 下:1、Route 中还有一个 render 属性,也可以用来渲染组件,但是当我们渲染被 Redux 处理过的组件时候可能会有问题,因为 Redux 会在原组件基础上多包裹一层。2、如果
当然常用路由组件还不仅仅这些,后续例子会有补充。
基础路由
基础路由的使用比较简单,前面的介绍其实已经把它基础使用方法已经说了。不过笔者这里有个小 tip:

match.url 常用于 NavLink 或者 Link 中拼接路由路径,比如:
<NavLink to={`${URL}/Home`}></NavLink>

match.path 常用于 Route 中拼接路由入口路径,比如:
<Route path={`${PATH}/Home`} component={HomePage} />
这里 Tip 下:当 URL 中不带有任何参数的时候,match.path 和 match.url 完全一致。如果带有参数的话可能会有点编码上的差异。
带参路由
带参路由在实际开发中用的比较多,这里只介绍 location.pathname 中的参数,location.search 笔者没有研究过所以就不说了,免得误导大家。我们可以先看下 demo 例子的部分代码:
<nav className={Style[“Params-nav-list”]}>
<ul>
<li><NavLink exact activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to={`${URL}/name`}>/name</NavLink></li>
<li><NavLink exact activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to={`${URL}/name/Mario`}>/name/Mario</NavLink></li>
<li><NavLink activeStyle={{fontWeight: ‘bold’, color: ‘red’}} to={`${URL}/check/true`}>/check/true</NavLink></li>
</ul>
</nav>
<div className={Style.content}>
<Switch>
<Route exact path={`${PATH}/name/:name`} component={Name} />
<Route exact path={`${PATH}/name`} component={Name1} />
<Route exact path={`${PATH}/check/:check(true|false)`} component={Check} />
<Route component={NoMatch} />
</Switch>
</div>
这里可以看到 Route 的配置有点不同,用过 express 的朋友都知道,路由中通过 :xxx 来标示这是一个参数。其实这里也一样。如例子所示我们在 path={${PATH}/name/:name}通过 :name 来标示这是一个参数。然后对应的导航也有 to={${URL}/name/Mario}。所以这个流程就相当于:导航到 /name 路由并且传 name 为 Mario 的参数。这个参数可以在对应组件的 props 中拿到(this.props.match.prams.name)。我们还看到 path={${PATH}/check/:check(true|false)},在参数后有个括号并且里面还有管道符,其实这是限定 check 的参数值必须为 true 或者 false 这两个。细心的朋友可能注意到,我在每个 Route 中加了 exact,这样做的好处是可以不用考虑 Route 的放置次序。举个例子解释下:如果我们想查看某个人的信息,那么跳转路由应该是 /user/4,但如果 Route 中有:
<Switch>
<Route exact path={/user} component={User} />
<Route exact path={/user/:id} component={User} />
</Switch>
那么 /user/ 4 就会在匹配到 /user 后停下渲染 User 组件并且忽略了参数。有人说把 Switch 去掉不就行了吗?并不是,那么 /user/ 4 会同时匹配上这两个路由并且什么都不会渲染,因为它懵逼了,不知道渲染哪个。所以这种情况下需要调整它们的位置
<Switch>
<Route exact path={/user/:id} component={User} />
<Route exact path={/user} component={User} />
</Switch>
这样就不会出现上述问题了。但是如果在每一个 Route 中使用 exact(前提是这个 Route 是叶子 Route),就不用考虑 Route 的次序问题了。
404 路由
有时候会由于各种问题出现匹配不到任何 Route 的情况,这个时候为了更好的用户体验,我们会配置一个 404 路由,形如:
<div className={Style.content}>
<Switch>
<Route exact path={`${PATH}/name/:name`} component={Name} />
<Route exact path={`${PATH}/name`} component={Name1} />
<Route exact path={`${PATH}/check/:check(true|false)`} component={Check} />
<Route component={NoMatch} />
</Switch>
</div>
不过笔者发现在根路由中配置一个 404 后无法全局抓取路由 404,不知道是本就如此还是用法有误,所以笔者在每一级路由中都配置了 <Route component={NoMatch} />。写法很简单,照着写就好了,component 中传入显示 404 信息的组件即可。
顺便加一下 demo 源码

退出移动版