8

类型安全、多路复用的 MobX + React 路由器

 3 years ago
source link: https://zhuanlan.zhihu.com/p/107334926
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

类型安全、多路复用的 MobX + React 路由器

JavaScript话题下的优秀回答者

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

  • 常见的 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 项目中我们就普遍使用了平行路由:

侧边栏(左)、 工作台(右)

这里说一个有趣的应用场景,目前我们的消息通知如果会打开特定视图/页面,则会在消息中储存一个视图对应路由的 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语雀或页面客服与我们建立连接。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK