重构 Angular 应用程序可能是一把双刃剑。一方面,它允许您提高代码库的可维护性和可扩展性。另一方面,如果您没有采取必要的预防措施来保护您的功能免受意外更改,则可能会导致路由中断。编写广泛的测试或为路由实现可靠的类型概念可以帮助减轻这种风险,但这些方法可能非常耗时,而且可能并不总是可行。在本文中,我们将探索一种更有效的解决方案,该解决方案可以在编译时自动检测损坏的路由,而无需手动测试工作或编写自定义类型注释。我们将通过实现具有嵌套组件的示例 Angular 应用程序并使用 typesafe-routes 库来演示这种方法,以改善开发人员体验并促进参数解析。
为了说明在编译时自动检测损坏的路由的好处,我们将实现一个具有三个嵌套组件的示例 Angular 应用程序:DashboardComponent (/dashboard)、OrgsComponent (/orgs/:orgId) 和 LocationsComponent (/orgs) /:orgId/locations/:locationId)。要设置此示例,我们需要安装 typesafe-routes 库并使用其 createRoutes 函数来定义我们的路由树,如以下代码片段所示。
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
让我们仔细看看代码片段。我们从 typesafe-routes 导入 createRoutes 并将我们的路由作为其第一个参数传递。这些路由被定义为一个嵌套对象,在根级别具有两个属性:仪表板和组织。每个属性都分配有一个路径,以数组的形式指定段。例如,[“dashboard”]数组对应于路径/dashboard。 orgs 路径更复杂,因为它包含一个名为 orgId 的整数类型参数。请注意,整数不是原生 JavaScript 类型,而是使用 int 函数定义的自定义类型,它在后台使用数字来模仿整数的特征。 orgs 路由有一个children 属性,它指定一个名为locations 的子路由。 Locations 路由与 orgs 路由类似,但它指定了一个额外的可选 int 类型的搜索参数页面。
createRoutes 使用有关路由的信息来创建包装在 Proxy 对象中的上下文。您不需要了解有关该代理对象的详细信息,但必须了解,由于该对象,您可以访问应用程序中任何位置的所有路由规范来渲染和解析路由和参数。
我们将createRoutes返回的Proxy对象分配给r。这意味着您可以使用 r.dashboard 访问仪表板路径,使用 r.orgs.locations 访问位置路径,等等。
定义了路由后,我们现在可以继续下一步:使用 Angular-router 注册它们。
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
代码片段显示了 Angular Router 的嵌套路由的常见设置,它反映了我们之前定义的路由树。但是,我们没有使用典型的纯字符串来指定路径模板(例如 orgs/:orgId),而是从 typesafe-routes/angular-router 导入模板函数并使用它来生成路径模板。对于DashboardComponent和OrgsComponent,我们可以简单地调用template及其对应的路径r.dashboard和r.orgs来获取模板。然而,剩下的组件 LocationsComponent 是 OrgsComponent 的子组件,因此需要一个相对路径,该路径不能通过使用 r.orgs.locations 生成,因为这会导致绝对路径 orgs/:orgId/locations/:locationId,而 Angular Router嵌套路由模板时需要相对路径。
要生成相对路径,我们可以使用 _ 链接,它有效地忽略下划线字符之前的所有内容。在这种情况下,我们可以使用 template(r.orgs._.locations) 来生成相对路径。这是一个方便的功能,因为它允许我们在需要渲染绝对路径的场景以及需要相对路径的情况下重用相同的路由树。
此时,我们已经在我们最喜欢的 IDE(例如 Visual Studio Code)中利用了自动完成和拼写错误预防功能。未来的变化将提醒我们路由路径中的任何拼写错误或拼写错误,因为所有类型都可以使用 createRoutes 追溯到初始路由定义。
现在我们已经指定了路线模板,我们要继续进行链接渲染。为此,我们希望创建一个简单的组件,利用渲染函数来渲染这些链接,包括类型序列化和类型检查。下一个示例显示了一个组件,该组件呈现引用我们应用程序中其他组件的锚元素列表。
// app.routes.ts import { Routes } from "@angular/router"; import { template } from "typesafe-routes/angular-router"; export const routes: Routes = [ { path: template(r.dashboard), // ~> "dashboard" component: DashboardComponent, }, { path: template(r.orgs), // ~> "orgs/:orgId" component: OrgsComponent, children: [ { path: template(r.orgs._.locations), // ~> "locations/:locationId" component: LocationsComponent, }, ], }, ];
代码示例从 typesafe-routes/angular-router 导入 render 和 renderPath。 renderPath 渲染路径,而 render 还序列化链接列表的查询参数。我们还导入 r,代理对象,它允许我们访问有关先前定义的路线的信息并定义要渲染的所需路线。
首先,我们使用 renderPath 函数创建dashboardLink 和orgsLink。作为第一个参数,它采用前面提到的代表要渲染的路线的路径的代理对象。第二个参数是一条记录,其参数值与之前在 app.routes.ts 中使用 createRoutes 定义的参数的名称和类型相匹配。返回值是一个字符串,包含属于相应组件的路径。
第三个示例中的渲染函数渲染路径和搜索参数,因此在参数定义中需要路径和查询属性。这里的返回值是一个对象,有path和query两个属性。我们将这两个属性设置为 [routerLink] 和 [queryParams] 属性的值。
参数解析是类型安全路由的重要组成部分。在上面的路由定义过程中,我们定义了几个参数并给它们一个类似整数的类型 int。但是,由于参数值来自各种来源(例如 Location 对象),因此它们是基于字符串的。方便的是,typesafe-routes 导出解析这些字符串并将其转换为所需类型的辅助函数。解析基于我们之前创建的代理对象 r,这意味着我们必须告诉库参数属于哪个路由。下一个示例通过显示两种常见的解析场景来演示这一点。
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
给定 location.href orgs/1/location/2?page=5,在 Angular 中,我们可以使用 this.route.snapshot.queryParams 访问基于字符串的查询参数,并且通过此提供基于字符串的路径参数。路线.快照.参数。将 parseQuery 与 r.orgs.locations 和 this.route.snapshot.queryParams 结合使用,我们可以检索页面参数为数字的对象。将 parsePath 与 r.orgs._.locations 和 this.route.snapshot.params 一起使用,我们得到解析后的 locationId。在这种情况下,r.orgs._.locations 是相对路径,并且 _ 链接之前的所有段都被省略,导致 orgId 不存在于结果对象中。
typesafe-routes 中的解析函数是通用的,我们还可以使用 parse 直接从 location.href 字符串中一次性提取所有参数。
// app.routes.ts import { Routes } from "@angular/router"; import { template } from "typesafe-routes/angular-router"; export const routes: Routes = [ { path: template(r.dashboard), // ~> "dashboard" component: DashboardComponent, }, { path: template(r.orgs), // ~> "orgs/:orgId" component: OrgsComponent, children: [ { path: template(r.orgs._.locations), // ~> "locations/:locationId" component: LocationsComponent, }, ], }, ];
可以通过 InferQueryParams、InferPathParams 或 InferParams 提取有关参数的类型信息。这是 InferQueryParams 实用程序类型的演示。
// app.component.ts import { render, renderPath } from "typesafe-routes/angular-router"; import { r } from "./app.routes"; @Component({ selector: "app-root", imports: [RouterOutlet, RouterLink], template: ` <h1>Absolute Links</h1> <ul> <li><a [routerLink]="dashboardLink">Dashboard</a></li> <li><a [routerLink]="orgsLink">Org</a></li> <li> <a [routerLink]="locationLink.path" [queryParams]="locationLink.query"> Location </a> </li> </ul> <router-outlet></router-outlet> `, }) export class AppComponent { dashboardLink = renderPath(r.dashboard, {}); // ~> dashboard orgsLink = renderPath(r.orgs, { orgId: 123 }); // ~> orgs/123 locationLink = render(r.orgs.locations, { path: { orgId: 321, locationId: 654 }, query: { page: 42 }, }); // ~> { path: "orgs/321/location/654", query: { page: "42" }} } // ...
为了结束本教程,我们创建了一个路由树 r,它是我们路由的唯一事实来源。基于此,我们渲染了用于向 Angular Router 注册组件的模板。我们使用动态路径段和查询参数渲染路径。我们解析参数,将它们从字符串值转换为相应的类型。我们以类型安全的方式完成了所有事情,甚至没有编写一个类型定义。我们建立了一个强大的路由树,可以在开发新功能时轻松防止错误,并进一步促进未来的重构。
但是,typesafe-routes 还有更多功能,例如许多不同的内置参数类型、自定义参数类型的轻松集成、子路径的操作、定义自定义模板字符串等等。不幸的是,我们无法在本教程中涵盖所有内容,但您可以通过访问官方文档了解更多信息。
当然,还可以对本教程中所示的示例实施许多潜在的改进。例如,用于链接呈现的自定义指令,它采用基于代理对象的路径定义,例如 r.orgs.locations。另一个例子是一个为 Angular Router 自动生成 Routes 数组的函数,有效地消除了重复的代码,并且无需使路由与第一个代码块中使用 createRoutes 创建的路由树保持同步。
但是,这些只是众多贡献方式中的几种。当然,最常见的方式是在我们的 GitHub 存储库中共享、报告错误或打开 PR。如果你使用这个库并认为它改善了你的开发体验,你也可以请我喝杯咖啡。我们还有一个 Discord 频道,您可以在其中留下反馈或提出问题。
以上是使用 Angular 中的类型安全路由消除运行时错误的详细内容。更多信息请关注PHP中文网其他相关文章!