路由

Rescript-React 附带路由!我们利用语言和库的特性,为了创建有以下优点路由:

  • 最简单、最轻薄。

  • 即插即用。

  • 小巧但性能强劲。

路由如何工作?

可用函数如下:

  • RescriptReactRouter.push(string):用新的路径更新 URL。

  • RescriptReactRouter.replace(string):类似 push,但是会替换当前 URL。

  • RescriptReactRouter.watchUrl(f):开始监听 URL 更改。返回一个订阅 token。当 URL 更改后,使用 RescriptReactRouter.url 记录作为参数调用回调函数。

  • RescriptReactRouter.unwatchUrl(watcherID):停止监听 URL 更改。

  • RescriptReactRouter.dangerouslyGetInitialUrl():在 watchUrl 之外,获取 url 记录。稍后进行说明。

  • RescriptReactRouter.useUrl(~serverUrl):返回组件内的 url 记录。

如果你想要了解更多路由接口的底层实现细节,请参考 RescriptReactRouter 实现

路由匹配

没有 API watchUrl 返回如下结构的 url 记录:

ReScriptJS Output
type url = {
  /* path 获取 window.location.pathname,类似"/book/title/edit",然后转换为 `list{"book", "title", "edit"}` */
  path: list<string>,
  /* url 的 hash,如果有的话。# 会被去除 */
  hash: string,
  /* url 的 query params,如果有的话。? 会被去除 */
  search: string
}

类似 www.hello.com/book/10/edit?name=Jane#author 这样的地址会被解析为:

ReScriptJS Output
{
  path: list{"book", "10", "edit"},
  hash: "author",
  search: "name=Jane"
}

基础示例

让我们看看 ReScript React 路由的例子:

ReScriptJS Output
// App.res
@react.component
let make = () => {
  let url = RescriptReactRouter.useUrl()
  
  switch url.path {
    | list{"user", id} => <User id />
    | list{} => <Home/>
    | _ => <PageNotFound/>
  }
}

直接获取路由

在特定场合中,你可能想在 watchUrl 之外操作 url 记录。例如,如果你将 watchUrl 放在组件的 didMount 中,从而在 URL 更改时触发组件的 state 更改,你可能还希望由 URL 决定初始状态。

换句话说,如果你需要在应用的逻辑开始时就读取 url 记录的内容,我们提供了 dangerouslyGetInitialUrl() 接口。

注意:我们将其标记为“危险”,是为了提醒你不要在任意组件的例如 render 内读取 url,因为如果组件不包含watchUrl 订阅来在 URL 变更时重新渲染,则获取的 url 信息可能已经过时。请你只将 dangerouslyGetInitialUrlwatchUrl 一起使用。

推送新路由

在应用的任何地方,只需要调用例如 RescriptReactRouter.push("/books/10/edit#validated") 的命令,就会触发 URL 变更(但页面不会刷新),而且 watchUrl 的回调会被调用。

我们可能会在未来为类型化路由 + 承载 payload 提供更好的基础设施!

注意:由于浏览器限制,无法检测到通过 JavaScript 更改的 URL (又叫 pushState)。解决方案是更改 URL,然后触发“popState”事件(被 watchUrl 监听)。这就是 RescriptReactRouter.push 的内部原理。 因此,如果出于一些原因(例如增量迁移),你需要绕过 RescriptReactRouter.push 更新 URL,使用 window.dispatchEvent(new Event('popState'))

设计决策

我们一直努力降低 Rescript-React 的性能开销和学习成本,路由设计也是一样。除浏览器特性检测外,整个路由的实现大约 20 行代码。回想起来,这样设计似乎是明确的,但是为了达到这个目的,我们必须深入挖掘 ReactJS 内部机制和 future proposals,来确保我们理解了状态更新机制,future context proposal,生命周期顺序等概念。并在这个过程中拒绝一些糟糕的 API 设计。很高兴能达成如此明确的解决方案!

路由 API 也没有规定路由匹配需要返回组件,状态更新,还是副作用。它的灵活性足以适配现有的应用。

在性能方面,类 JavaScript API 倾向于使用路由字符串的 JS 对象 -> 回调。为了支持模式匹配,我们不会那样做,因为在 ReScript 中后者不分配内存,而是在 C++ 中(通过 JS JIT)编译为快速跳转表。实际上,在路由匹配中只有创建 url 记录时进行了内存分配。