react Link跳转无效_类型安全、多路复用的 MobX + React 路由器

路由器是前端生态中非常常用,通常非常成熟的一个组成部分。于是为“什么还要造这么一个轮子”成了必须要回答的灵魂拷问。最初驱使我去做这件事情的大致是以下几个原因:

  • 常见的 React Router 方案没有中心化的路由状态,不方便在组件外与全局状态/服务集成。有时还需要自行、多次处理查询参数,写出来的代码有冗余的感觉。
  • 当时还没有看到类型安全的路由实现,路径基本就是直接写字符串。

随着项目迭代,又有了一个非常好的原因:

  • 没有看到支持平行路由的路由实现。

Boring Router 是基于 React + MobX 的路由器,使用 TypeScript 开发,实现了类型安全的路由使用,支持互相独立平行路由。目前 Boring Router 已经经过了一年半的迭代,在我们的内部项目中广泛使用。

与常见的 React 生态中组件优先的路由器不同,Boring Router 关注的是路由状态,组件实现非常轻量。基本用法如下:

import {RouteMatch, Router} from 'boring-router';
import {BrowserHistory, Link, Route} from 'boring-router-react';
import {observer} from 'mobx-react';
import React, {Component} from 'react';

// 创建浏览器历史对象,该对象对历史操作进行了包装,方便修改和恢复特定历史状态
const history = new BrowserHistory();

// 创建路由器对象
const router = new Router(history);

// 创建 route 主路由,该路由包含了 account、about 以及 notFound 三个子项
const route = router.$route({
  account: true, // true 是简写,表示该路由项使用默认选项
  about: true,
  notFound: {
    // $match 可以是固定的字符串或正则表达式,默认是该路由项的键,这里使用自带的正则 rest /.+/
    $match: RouteMatch.rest,
  },
});

@observer
class App extends Component {
  render() {
    return (
      <>
        {/* 使用 Route 组件的 match 属性来匹配特定路由项 */}
        <Route match={route.account}>
          Account page
          <hr />
          {/* 使用 Link 组件的 to 属性来指定链接地址 */}
          <Link to={route.about}>About</Link>
        </Route>
        <Route match={route.about}>About page</Route>
        <Route match={route.notFound}>Not found</Route>
      </>
    );
  }
}

简单来说,通过上面 router.$route() 创建出的路由属性及其子路由项属性都是 observable 的。以 route.about 为例, route.about.$matched 属性会根据当前路由的匹配状况变化,而 Route 组件则会随着传入路由项的 $matched 值来渲染或忽略组件内容。

这意味着,除了 Route 组件,我们可以在任何地方直接使用路由对象来判定是否匹配,也可以对参数信息进行读取。这也是 Boring Router 和类似 React Router 的路由器最大的不同:Boring Router 具有中心化且对外暴露的路由状态管理。

看完了面相,接下来将会介绍 Boring Router 的特色能力。

类型安全

Boring Router 基于 TypeScript 开发,API 在设计时以类型安全作为优先考虑(当然这也带来了一些用法上的妥协)。子项和参数(包括路径参数和查询参数)类型都通过路由定义的对象字面量类型进行计算获得。

在使用路由时,几乎所有场景下都无需直接使用字符串。除前端使用外,也可以通过共享路由定义的方式在后端使用,通过路由对象在运行时构建路径字符串:

import {Router, ReadOnlyHistory} from 'boring-router';
import {routeSchema} from 'shared';

const history = new ReadOnlyHistory();

export const router = new Router(history);
export const route = router.$route(routeSchema);

如果需要生成路径字符串,调用 route 对象的 $href() 方法即可:

let path = route.about.$href();

如此一来,有路径更新,就不用再担心改漏地方了。

平行路由

当页面由多个相对独立的平行视图组成时,常见的路由好像都没有提供分别表达不同视图路径的能力。以 React Router 文档例子中的 Modal Gallery 为例,在处理平行的视图时,需要小心维护额外的状态,换言之其本身并没有直接提供相应的支持。

Boring Router 针对这种平行视图的情况,添加了“平行路由”的支持:

const router = new Router<'sidebar' | 'overlay'>(history);

// 主路由
const route = router.$route({/* ... */});

// 侧边栏路由
const sidebarRoute = router.$route('sidebar', {/* ... */});

// 覆盖层路由
const overlayRoute = router.$route('overlay', {/* ... */});

平行路由地使用和主路由基本一致,除了查询参数(?foo=bar)需要和主路由共用。在地址栏中,平行路由的路径以 _ 开头的组名称表示,如 ?_sidebar=/foo&_overlay=/bar。比如在 Makeflow 项目中我们就普遍使用了平行路由:

403e88a5e820f01e5a791bca5f79ecd0.png
侧边栏(左)、 工作台(右)

这里说一个有趣的应用场景,目前我们的消息通知如果会打开特定视图/页面,则会在消息中储存一个视图对应路由的 ref(route.xxx.$ref())。以侧边栏视图为例,消息中储存的 ref 就是 ?_sidebar=/xxx。前端在打开消息时,只需 router.$push(ref) 即可。由于这里的 ref 不以 / 开头,Boring Router 会认为它仅包含平行路由部分,于是不会改变当前页面的主路由路径。

生命周期

我们花了不少精力自行实现了 Boring Router 中的 BrowserHistory 对象,在其中对页面导航进行追踪,实现了历史状态恢复能力(不仅是当前路径,还有历史记录)。

得益于此,Boring Router 可以提供更丰富的生命周期 API:

  • $beforeEnter/Update$afterEnter/Update (可异步)
  • $beforeLeave$afterLeave

before* 钩子中,可以随时终止跳转或重定向到其他路由:

route.xxx.$beforeEnter(() => {
  if (Math.random() < 0.5) {
    route.yyy.$replace();
  }
});

route.xxx.$beforeLeave(() => Math.random() < 0.5);

为了方便复用和组织生命周期钩子,Boring Router 提供了“路由服务”,可以通过 route.xxx.$service() 定义。通过路由服务,还可以实现参数的转换,方便视图直接使用转换后的对象。具体使用可以参考路由服务例子。


以上就是对 Boring Router 的简单介绍了,如果你有新项目使用 MobX + React + TypeScript,那 Boring Router 说不定是一个可以踩踩的坑。 使用中如果有任何问题可以提 issue,我也会在第一时间解答。


Makeflow (makeflow.com) 是以流程为核心的项目管理工具,让研发团队能更容易地落地和改善工作流,提升任务流转体验及效率。如果你正为了研发流程停留在口头讨论、无法落实而烦恼,Makeflow 或许是一个可以尝试的选项。如果你认为 Makeflow 缺少了某些必要的特性,或者有任何建议反馈,可以通过 GitHub、语雀或页面客服与我们建立连接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值