ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Wasp 类型安全链接(Type-Safe Links)实战指南:用 `@wasp/router` 告别手写字符串路由

Wasp 类型安全链接(Type-Safe Links)实战指南:用 `@wasp/router` 告别手写字符串路由 Wasp 类型安全链接Type-Safe Links实战指南用wasp/router告别手写字符串路由【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/waspWasp 允许你在main.wasp中以声明式方式定义路由与页面而wasp/router提供的类型安全Link组件与routes对象能让前端跳转在编译期就校验路径与参数的正确性。本文基于 Wasp 0.11.8 版本文档展开并结合当前仓库中 SDK 生成模板与 Haskell 生成器源码讲清类型安全链接的用法、API 细节与底层实现原理读完后你将能在自己的 Wasp 应用中写出参数必填、永不写错路径的页面跳转代码。为什么需要类型安全链接在传统 React 应用中页面跳转通常是这样写的Link to{/task/${task.id}?sortBydate#comments}{task.description}/Link路径是纯字符串一旦路由路径在main.wasp中调整例如把/task/:id改成/tasks/:taskId或者参数名拼写错误编译期不会有任何提示只有运行时才会出现 404 或空白页。Wasp 的解决思路是既然路由是在main.wasp中以声明式route声明定义的那么生成器在构建项目时可以把每个路由的路径、参数名、可选参数等全部转成 TypeScript 类型。你在前端使用Link组件或routes对象时编辑器与编译器就能逐字校验路径合法性并强制你补全所有必需的params——这就是类型安全链接的核心价值。前置在main.wasp中声明路由类型安全链接的第一步是在 Wasp 的 DSL 文件0.11.8 时代为main.wasp当前仓库示例使用main.wasp.ts中定义路由。路由通过route声明将 URL 路径映射到某个pageroute TaskRoute { path: /task/:id, to: TaskPage } page TaskPage { ... }其中path支持以下段类型解析逻辑见 waspc/src/Wasp/Util/WebRouterPath.hs静态段如/task中的task即普通字符串片段必选参数段:id对应RequiredParamSegment调用时必须传入{ id: ... }可选参数段:id?对应OptionalParamSegment传不传都合法可选静态段如/users/tasks?/:id?中的tasks?对应OptionalStaticSegment生成器会将其展开为/users/tasks/:id?与/users/:id?等多条可选路径组合通配符段*可匹配任意剩余路径对应RequiredParamSegment *。从源码结构看Wasp 在编译期就完整解析了这些段类型这正是它在生成代码时能精确推导参数类型的前提。使用Link组件创建类型安全链接定义好路由后在前端组件中导入wasp/router的Link组件import { Link } from wasp/router export const TaskList () { // ... return ( div {tasks.map((task) ( Link key{task.id} to/task/:id {/* 必须提供一个合法路径 */} params{{ id: task.id }} {/* 路径中的所有参数都必须正确传入 */} {task.description} /Link ))} /div ) }两个关键点to必须是main.wasp中声明过的合法路由路径。Wasp 会基于生成的路由类型做校验写错路径比如漏掉:id会立刻在类型检查时报错params必须覆盖路径中的所有参数。to写的是带:id占位符的模板路径params提供实际值二者由类型系统绑定在一起缺参数或多传参数都无法通过编译。使用 Search Query 与 Hash除了路径参数Link还支持search与hash两个属性分别对应 URL 的查询字符串与锚点Link to/task/:id params{{ id: task.id }} search{{ sortBy: date }} hashcomments {task.description} /Link最终渲染出的链接形如/task/1?sortBydate#comments。search支持所有URLSearchParams构造器可接受的输入形式下文 API Reference 会给出完整类型。使用routes对象构建链接字符串当你不渲染Link而是需要拿到一个链接字符串例如传给第三方组件或进行编程式跳转时可以改用wasp/router导出的routes对象import { routes } from wasp/router const linkToTask routes.TaskRoute.build({ params: { id: 1 } })结果同样是/task/1。build函数同样支持search与hashconst link routes.TaskRoute.build({ params: { id: task.id }, search: { sortBy: date }, hash: comments, })routes对象中每个路由对应一个同名的build函数键名即main.wasp中的路由名如TaskRoute。从当前仓库的 SDK 生成模板 waspc/data/Generator/templates/sdk/wasp/client/router/index.ts 可以看到生成器会依据路由是否包含参数决定build的签名路由不含参数时build的入参是可选对象仅包含search与hash路由含参数时build的入参强制包含params且params中每个键的类型都从路径解析而来。API ReferenceLink组件Link组件接受以下属性to必填来自main.wasp文件中声明的合法 Wasp 路由路径。params: { [name: string]: string | number }路径包含参数时必填为路径中的每个参数提供键值对。例如路径为/task/:id时params必须是{ id: 1 }。Wasp 同时支持必选参数与可选参数/task/:id/:something?中something即可选。search: string[][] | Recordstring, string | string | URLSearchParams任何URLSearchParams构造器可接受的合法输入。例如对象{ sortBy: date }会生成?sortBydate。hash: string追加到 URL 末尾的锚点片段。其余属性react-router-dom的Link组件支持的其他属性均可透传如target、rel、onClick等。在 0.11.8 版本文档对应的时代底层依赖为react-router-dom从当前仓库主分支的 SDK 模板 waspc/data/Generator/templates/sdk/wasp/client/router/Link.tsx 看实现已迁移为基于react-router的RouterLink并通过interpolatePath完成路径、查询串与锚点的拼接其余属性通过...restOfProps原样透传。routes对象routes对象为应用中的每个路由提供一个build函数其生成类型大致如下export const routes { // RootRoute 的路径形如 / RootRoute: { build: (options?: { search?: string[][] | Recordstring, string | string | URLSearchParams hash?: string }) // ... }, // DetailRoute 的路径形如 /task/:id/:something? DetailRoute: { build: ( options: { params: { id: ParamValue; something?: ParamValue }, search?: string[][] | Recordstring, string | string | URLSearchParams hash?: string } ) // ... } }规则如下路由包含参数时params对象必填ParamValue即string | number见 types.tssearch与hash始终可选路由不含参数时整个 options 对象都是可选的。用法示例import { routes } from wasp/router const linkToRoot routes.RootRoute.build() const linkToTask routes.DetailRoute.build({ params: { id: 1 } })底层实现链接是如何被类型安全地生成出来的理解类型安全链接的机制只需看三个环节路由信息的提取、生成模板的渲染、以及运行时拼接。1. 生成器从 AppSpec 提取路由信息Wasp 编译器在构建时会先把main.wasp或main.wasp.ts解析为 AppSpec。SDK 生成器 RouterGenerator.hs 遍历AS.getRoutes spec为每个路由提取name路由名如TaskRouteurlPath原始路径字符串urlParams路径中的参数列表并逐一标记isOptional对应:id?这类可选参数hasUrlParams与hasOptionalStaticSegments用于驱动模板分支。这些信息被序列化为 JSON 数据交给模板引擎渲染。2. 模板渲染出routes对象与Link组件模板 index.ts 根据上述数据为每个路由生成{ to: ..., build: (...) interpolatePath(...) }并通过hasUrlParams分支决定build是否需要强制params// 含参数的路由简化示意 { name }: { to: { urlPath }, build: (options: OptionalRouteOptions { params: {...} }) interpolatePath({ urlPath }, options.params, options?.search, options?.hash), }Link与NavLink组件则分别包装react-router的RouterLink/RouterNavLink将to、params、search、hash组合后作为最终的to传入见 Link.tsx 与 NavLink.tsx。3. 运行时interpolatePath完成字符串拼接模板与组件最终都会调用interpolatePathlinkHelpers.tsconst interpolatedPath params ? interpolatePathParams(path, params) : path const interpolatedSearch search ? ?${new URLSearchParams(search).toString()} : const interpolatedHash hash ? #${hash} : return interpolatedPath interpolatedSearch interpolatedHashinterpolatePathParams会把路径按/切分将:name替换为params[name]、把*替换为params[*]并通过filter(isValidPathPart)过滤掉未提供的可选参数段。也就是说可选参数如/task/:id/:something?即使不传也能拼出合法的/task/1。4. 类型层面的强制校验类型安全的最后一道保障来自 types.tsRouteDefinitionsToRoutes从生成的routes对象反推出所有路由的联合类型ParamsFromBuildFn通过ParametersBF[0] extends { params: infer Params }提取每个build的参数类型——含参数的路由params为必填类型不含参数的则被推导为params?: never从而杜绝漏传ExpandRouteOnOptionalStaticSegments会把带可选静态段的路由如/users/tasks?/:id?在类型层面展开为多条路径组合保证to的字符串字面量也能被精确校验。实战建议与注意事项优先使用routes对象而非硬编码字符串在需要程序化跳转如navigate(routes.TaskRoute.build(...))或把链接作为数据传递时build能同时兼顾类型安全与字符串输出。search的多种传法按需选择简单的键值对用对象即可需要重复键或精确控制编码顺序时可用string[][]或URLSearchParams实例。善用可选参数与可选静态段它们能显著提升路径的灵活度但注意可选参数在interpolatePath中会被静默过滤若页面逻辑强依赖某个参数存在请尽量把它声明为必选。版本差异0.11.8 文档基于react-router-dom编写当前仓库主分支的 SDK 已迁移到react-routerLink/NavLink均重新导出但to/params/search/hash的用法与 API 保持一致迁移成本极低。可继续研读的仓库入口路由文档当前版本、Link 组件实现、routes 生成模板、路径解析源码以及 RouterGenerator.hs可以帮助你深入理解从 DSL 声明到前端类型推导的完整链路。【免费下载链接】waspThe batteries-included full-stack framework for the AI era. Develop JS/TS web apps (React, Node.js, and Prisma) using declarative code that abstracts away complex full-stack features like auth, background jobs, RPC, email sending, end-to-end type safety, single-command deployment, and more.项目地址: https://gitcode.com/GitHub_Trending/wa/wasp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进