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-routerreact-router-domreact-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的使用:

clipboard.png

如图是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>

从这段代码中需要了解的概念包括:RouterNavLinkRouteRedirectexactSwitch

Router

上面已经介绍过了,这里笔者用的是BrowserRouter

NavLink

NavLink可以触发路由的跳转,当然类似的组件还有Link。它们都可以通过指定属性to的值来告诉Router我们要跳转到那个Route,实际上NavLink(Link)Route本就已经通过topath两个属性建立起关系了。而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} />;

那么传入的参数为:
clipboard.png

所以如果在跳转前有什么特殊逻辑需要处理,比如我们想让不同的页面有不同的前缀,比如例子中的/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源码


风吹过的夏夜
295 声望31 粉丝

前端工程师