解决TypeScript报错:Property 'sma'不存在于TypedResponse<never>类型
我有一个服务端数据loader,从数据模型中获取用户ID并校验其是否存在:
export const loader = async ({ request, params }: LoaderArgs) => { const REQUESTED_USER = await GetUserById(Number(params.id)) if (!REQUESTED_USER) { /** * If the provided url params user ID does not exist, we will handle it here */ return redirectWithError({ request, message: `User ID: ${params.id} was not found; please enter a valid User ID`, logMessage: `User ID: ${params.id} was not found; please enter a valid User ID`, redirectTo: '/manager' }) } // 后续数据处理逻辑 if (GET_SELECTED_SERVICES) { sma = unwrap_sma[0] edr = unwrap_edr[0] cma = unwrap_cma[0] vms = unwrap_vms[0] } return typedjson({ sma, vms, cma, edr, user: REQUESTED_USER }) }
REQUESTED_USER的类型为:
const REQUESTED_USER: { id: number; company_name: string | null; avatar: string | null; brand: string | null; email: string; name: string | null; favicon: string | null; } | null
之后在客户端React组件中接收loader返回的数据:
export const ReadUser = ({ }) => { const LOADER = useTypedLoaderData<typeof loader>(); const [CSRF] = useCSRF() return ( <main className="flex w-full"> <Form method="post" className="flex w-full"> <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly /> <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} /> </Form> </main> ) }
此时Profile组件的属性抛出TypeScript错误:
Property 'sma' does not exist on type 'TypedResponse<never> | TypedJsonResponse<{ sma: TOptions[]; vms: TOptions[]; cma: TOptions[]; edr: TOptions[]; user: { id: number; company_name: string | null; ... 4 more ...; favicon: string | null; }; }>'. Property 'sma' does not exist on type 'TypedResponse<never>'.ts(2339)
LOADER的类型为:
const LOADER: TypedResponse<never> | TypedJsonResponse<{ sma: TOptions[]; vms: TOptions[]; cma: TOptions[]; edr: TOptions[]; user: { id: number; company_name: string | null; ... 4 more ...; favicon: string | null; }; }>
我尝试将!REQUESTED_USER改为REQUESTED_USER === null,但没有效果,希望了解错误原因并解决。
TypeScript报错的核心是:它无法在编译阶段确认,当组件执行到读取LOADER.sma的逻辑时,loader一定返回了带sma的JSON响应。
在loader代码中,当REQUESTED_USER不存在时,会返回redirectWithError(对应类型TypedResponse<never>);只有校验通过后,才会返回带sma等字段的TypedJsonResponse。但在客户端组件中,TypeScript仅会依据loader的返回类型集合进行判断,不会自动推断出“组件能渲染就说明校验已通过、不会返回TypedResponse<never>”这一运行时逻辑,因此认为LOADER仍有可能是不包含sma属性的TypedResponse<never>类型,进而抛出错误。
方案1:使用类型断言明确类型
直接告知TypeScript当前LOADER就是带数据的TypedJsonResponse类型:
export const ReadUser = ({ }) => { const LOADER = useTypedLoaderData<typeof loader>() as TypedJsonResponse<{ sma: TOptions[]; vms: TOptions[]; cma: TOptions[]; edr: TOptions[]; user: { id: number; company_name: string | null; avatar: string | null; brand: string | null; email: string; name: string | null; favicon: string | null; }; }>; const [CSRF] = useCSRF() return ( <main className="flex w-full"> <Form method="post" className="flex w-full"> <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly /> <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} /> </Form> </main> ) }
方案2:添加运行时校验(更安全)
先判断LOADER是否包含sma属性,再渲染组件,同时让TypeScript自动收窄类型:
export const ReadUser = ({ }) => { const LOADER = useTypedLoaderData<typeof loader>(); const [CSRF] = useCSRF() // 若loader返回的是redirect响应,这里可以做兜底处理 if (!('sma' in LOADER)) { return <div>加载失败,请重试</div>; } return ( <main className="flex w-full"> <Form method="post" className="flex w-full"> <input className="hidden" name={'csrf'} value={CSRF} type="text" readOnly /> <Profile SMA={LOADER.sma} EDR={[]} VMS={[]} CMA={[]} /> </Form> </main> ) }
方案3:优化loader的类型推断(推荐)
改用throw方式抛出redirect响应(这是Remix等框架的标准做法),这样TypeScript会识别出抛出异常后后续代码不会执行,从而自动收窄loader的返回类型:
export const loader = async ({ request, params }: LoaderArgs) => { const REQUESTED_USER = await GetUserById(Number(params.id)) if (!REQUESTED_USER) { // 改用throw方式抛出redirect,而非返回 throw redirectWithError({ request, message: `User ID: ${params.id} was not found; please enter a valid User ID`, logMessage: `User ID: ${params.id} was not found; please enter a valid User ID`, redirectTo: '/manager' }) } // 初始化变量避免类型报错 let sma: TOptions[] = []; let edr: TOptions[] = []; let cma: TOptions[] = []; let vms: TOptions[] = []; if (GET_SELECTED_SERVICES) { sma = unwrap_sma[0] || []; edr = unwrap_edr[0] || []; cma = unwrap_cma[0] || []; vms = unwrap_vms[0] || []; } return typedjson({ sma, vms, cma, edr, user: REQUESTED_USER }) }
此时客户端的useTypedLoaderData<typeof loader>会自动推断出返回类型仅为TypedJsonResponse,不再包含TypedResponse<never>,错误自然消失。
内容的提问来源于stack exchange,提问作者T. Duke

