TanStack Router 的 isRedirect 函数:识别与收窄 Redirect 对象的类型守卫
【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router
isRedirect是 TanStack Router(本仓库router-core包)提供的运行时类型守卫,用于判断一个值是否是框架生成的 redirect 对象。它最常在 loader/beforeLoad 返回值、throw redirect(...)被 catch 到的异常、或中间件结果等“值类型未知(unknown)”的边界处使用,帮助你在不确定对象来源时安全地做重定向分支处理。读完本文,你可以掌握isRedirect的签名与参数、其底层判定原理(Response实例 +options标记),以及它在客户端加载管线、服务端渲染和 Start 框架中间件中的真实调用位置。
什么是 isRedirect
isRedirect函数用于确定一个对象是否为 redirect 对象。在 TanStack Router 中,redirect()工厂函数(详见 RedirectType 文档 与 redirect.ts)生成的 redirect 对象本质上是一个被扩展了导航选项的Response实例。当你拿到一个来源不确定的值——比如从 loader 透传出来的unknown、被try/catch捕获的异常对象、或从序列化数据还原回来的对象——就需要isRedirect来做可靠的运行时判别,而不是依赖instanceof某个自定义类。
它是与isNotFound并列的控制流判别工具:两者共同覆盖路由数据加载管线中“不是普通返回值,而是导航指令”的两种特殊结果。
isRedirect 的签名与参数
根据 API 文档(isRedirectFunction.md):
isRedirect接受单个参数input:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
input | unknown | 是 | 待检查是否为 redirect 对象的对象 |
返回值:
- 类型:
boolean - 对象是 redirect 对象时返回
true - 否则返回
false
文档示例:
import { isRedirect } from '@tanstack/react-router' function somewhere(obj: unknown) { if (isRedirect(obj)) { // 此处 obj 被收窄为 redirect 对象 // ... } }值得注意的是,isRedirect不仅返回布尔值,在 TypeScript 层面它是一个类型守卫(type predicate)。从 redirect.ts 源码看,实际签名如下:
/** Check whether a value is a TanStack Router redirect Response. */ export function isRedirect(obj: any): obj is AnyRedirect { return obj instanceof Response && !!(obj as any).options }这意味着在if (isRedirect(obj))分支内,obj的类型会从unknown/any被自动收窄为AnyRedirect,你可以直接访问它的options、status、headers等成员而不需要额外断言。AnyRedirect即Redirect<any, any, any, any, any>,是所有路由/来源泛型实例化的联合形态,定义于 redirect.ts。
底层原理:为什么是instanceof Response && options
理解isRedirect的关键,是理解redirect()工厂如何构造对象。阅读 redirect.ts:
export function redirect<...>(opts: RedirectOptions<...>): Redirect<...> { // 1. 状态码默认 307(也可用已废弃的 code 字段) opts.statusCode = opts.statusCode || opts.code || 307 const headers = new Headers(opts.headers) // 2. 外部 href 自动写入 Location 头 if (opts.href && headers.get('Location') === null) { headers.set('Location', opts.href) } // 3. 构造一个标准 Response const response = new Response(null, { status: opts.statusCode, headers, }) // 4. 把导航选项挂到 Response 实例上,作为"是路由重定向"的标记 ;(response as Redirect<...>).options = opts // 5. 支持 throw 语义:throw redirect({ throw: true }) if (opts.throw) { throw response } return response as Redirect<...> }由此得到三条判定链上的事实:
- redirect 对象首先是
Response实例。它携带 HTTP 语义(status、headers),这使得 redirect 天然可以在服务端作为标准响应返回,也能在客户端导航中被路由消费。 .options是路由专属的标记字段。普通new Response()或 fetch 拿到的响应不会有options属性,因此isRedirect可以把它和一切"看起来像 Response 但不是重定向"的值区分开。Redirect类型本身(见 redirect.ts)定义为Response & { options: NavigateOptions<...> }——即"一个带有导航选项的 Response",与isRedirect的运行时检查一一对应。
options中保留了redirect()调用时的完整入参:to/href、params、search、replace、statusCode、headers等标准导航选项,以及throw语义。这就是为什么框架内部在处理重定向时能直接从对象上"还原"出目标路由。
另外两个同文件导出的相关工具可以顺带了解(redirect.ts):
isResolvedRedirect(obj):在isRedirect基础上进一步判断obj.options.href是否存在,即该重定向是否指向外部 URL;parseRedirect(obj):从序列化后的普通对象(带isSerializedRedirect标记)还原出 redirect 对象,用于跨进程传递重定向的场域。
框架内部的真实调用位置
isRedirect不只是给开发者用的工具,它也是路由核心管线识别重定向的控制点。以下调用链均可在仓库源码中逐一验证:
客户端加载管线:loader/beforeLoad 结果归一化
在 load-client.ts 的normalize函数中,每个路由加载器的返回值/抛出的值都会被分类:
function normalize(value: unknown, rejected: boolean, routeId?: string): RawLoaderOutcome { if (isRedirect(value)) { return [REDIRECTED, value] } if (isNotFound(value)) { value.routeId ||= routeId return [NOT_FOUND, value] } if (!rejected) { return [SUCCESS, value] } // ... return [ERROR, value] }可以看到:无论重定向对象是被返回的(return redirect(...))还是被抛出的(throw redirect({ throw: true }),此时走rejected: true分支),只要通过isRedirect识别,都会被归一化为REDIRECTED结果,再交给后续的materializeRedirect流程驱动导航。这正是redirect({ throw: true })能与return redirect(...)等价工作的底层原因。
严格参数解析中的重定向放行
在 router.ts 中,路由参数的严格解析(extractStrictParams,通常由 search/params 校验器触发)会捕获错误,并专门放行isNotFound与isRedirect:
try { extractStrictParams(route, strictParams) } catch (err: any) { if (isNotFound(err) || isRedirect(err)) { paramsError = err } else { paramsError = new PathParamError(err.message, { cause: err }) } // ... }也就是说,如果你的 search 校验器在参数不合法时throw redirect(...)(例如"该用户应跳转到其专属页"),路由器会识别这是一个重定向并正常处理,而不是把它包装成PathParamError走错误界面。isRedirect在这里起到了"控制流异常穿透"的作用。
服务端渲染管线
服务端的 loader 归一化逻辑与客户端对称,位于 load-server.ts:
function normalize(value: unknown, rejected: boolean): LoaderOutcome { if (isRedirect(value)) { return [REDIRECTED, value] } if (isNotFound(value)) { return [NOT_FOUND, value] } // ... }SSR 过程中 loader 抛出的重定向同样经由isRedirect被识别,最终以{ type: 'redirect'; redirect: AnyRedirect }的结果形态参与响应决策。
Start 框架中间件:识别"特殊响应"
在@tanstack/react-start等服务端框架的处理器中,createStartHandler.ts 定义了:
function isSpecialResponse(value: unknown): value is Response { return value instanceof Response || isRedirect(value) }从源码结构看,isRedirect被用于判断中间件/处理函数返回的未知值是否是一个需要按 HTTP 响应特殊处置的对象(例如延迟响应、不可被 defer 的分支)。换言之,中间件直接return redirect(...)的写法能够生效,也依赖这一识别逻辑。
典型实战用法
1. 处理来源未知的返回值或异常
当你编写一个复用函数接收 loader 结果,或需要在catch块中区分"业务错误"与"重定向"时:
import { isRedirect, isNotFound } from '@tanstack/react-router' async function handleLoaderResult(result: unknown) { if (isRedirect(result)) { // result 已收窄为 AnyRedirect,可读取 status/headers/options console.log('redirect to', result.options.href ?? result.options.to, result.status) return } if (isNotFound(result)) { // 404 分支 return } // 普通数据 }2. 在 beforeLoad 中透传上游抛出的重定向
beforeLoad里若调用其他可能抛重定向的逻辑,需要让重定向继续向上抛而不是被吞掉:
const settingsRoute = createRoute({ id: 'settings', beforeLoad: async ({ context }) => { try { await requireAuth(context) // 内部可能 throw redirect({ href: '/login' }) } catch (err) { if (isRedirect(err)) { throw err // 透传重定向,交由路由器处理 } throw err } }, // ... })由于框架在normalize阶段对"rejected"的值同样执行isRedirect检查(见上文 load-client.ts),透传即可,无需自行转换为返回值。
3. 与 isResolvedRedirect 区分内外部重定向
import { isResolvedRedirect, redirect } from '@tanstack/react-router' if (isResolvedRedirect(value)) { // value.options.href 必然存在,是外部 URL 重定向 // 例如:redirect({ href: 'https://auth.example.com' }) }外部href重定向在redirect()创建时已自动写入Location请求头(见 redirect.ts),服务端可直接将该Response作为 HTTP 响应返回。
使用注意事项与边界
- 判定条件是"双重的":
obj instanceof Response && obj.options。一个手写的普通new Response()不会命中;一个普通对象(例如序列化后的{ href, statusCode })也不会命中。跨序列化边界的重定向应先经parseRedirect还原再判断。 Redirect类型即Response & { options: NavigateOptions }(redirect.ts)。API 文档 RedirectType.md 描述的是redirect()的入参形态(statusCode/throw/headers+ 导航属性),实际运行时的对象结构以源码为准。- 默认状态码为 307:
redirect()在未显式提供statusCode时回退到 307(redirect.ts),code字段已被标记为废弃,新代码应使用statusCode。 isRedirect在各框架包中均有再导出:@tanstack/react-router、@tanstack/solid-router、@tanstack/vue-router的入口(如 react-router/src/index.tsx)都从@tanstack/router-core转出了该函数(见 router-core/src/index.ts),因此无论使用哪个框架绑定,导入方式一致。
小结
isRedirect以两行源码(instanceof Response+options标记)承担了 TanStack Router 中"重定向识别"的全部职责:它是 TypeScript 的类型收窄守卫,是客户端/服务端 loader 归一化的分派依据,是严格参数解析中控制流异常的放行通道,也是 Start 框架中间件识别特殊响应的组件。掌握它之后,你可以在任何unknown边界处可靠地检测、收窄并透传重定向对象。
【免费下载链接】router🤖 A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考