Type narrowing深度嵌套对象时出现any值不安全访问错误如何解决
报错原因
- 可选链表达式的类型收窄不持久:你每次使用
err.response?.data都是独立的表达式,TypeScript无法保证两次访问之间err.response的值没有发生变更,所以第一次判断生效的类型收窄,不会应用到后续的同表达式访问上。 - 未排除null的情况:
typeof null === 'object'是JS的历史遗留特性,你现有判断逻辑无法排除data === null的情况,此时访问null.errors本身就是非法操作,TS会识别到这个风险。 - 基础object类型没有属性定义:就算排除了null,你收窄得到的
object类型是没有定义任何属性的,直接访问errors属性不符合类型检查规则,会触发任意值成员访问的报错。
直接修复方案
将每层嵌套值提前提取到局部常量,提前做非空判断,类型收窄会对局部常量生效,即可解决报错:
const getApiError = (err: unknown): ApiError | null => { if (!axios.isAxiosError(err)) return null const { response } = err if (!response) return null const { data } = response if (data === null || typeof data !== 'object') return null if (!('errors' in data) || !Array.isArray(data.errors) || data.errors.length === 0) { return null } return data.errors[0] as ApiError }
更优雅的深层校验方案
- 自定义类型守卫:将固定的结构校验逻辑封装为可复用的类型守卫,代码更简洁,类型收窄可自动生效,示例如下:
interface ApiErrorResponse { errors: ApiError[] } function isApiErrorResponse(data: unknown): data is ApiErrorResponse { return ( data !== null && typeof data === 'object' && 'errors' in data && Array.isArray(data.errors) && data.errors.length > 0 ) } // 业务代码可简化为 const getApiError = (err: unknown): ApiError | null => { if (axios.isAxiosError(err) && isApiErrorResponse(err.response?.data)) { return err.response.data.errors[0] } return null }
- 运行时结构校验库:使用Zod这类运行时类型校验库,只需要定义一次结构Schema,可同时覆盖运行时校验和静态类型推导,不需要手动编写大量判断逻辑,适配复杂结构校验场景的维护成本更低,示例如下:
import { z } from 'zod' // 定义ApiError结构 const ApiErrorSchema = z.object({ code: z.number(), message: z.string() }) // 定义响应体结构 const ApiErrorResponseSchema = z.object({ errors: z.array(ApiErrorSchema).nonempty() }) const getApiError = (err: unknown): ApiError | null => { if (!axios.isAxiosError(err)) return null const parseResult = ApiErrorResponseSchema.safeParse(err.response?.data) return parseResult.success ? parseResult.data.errors[0] : null }
内容的提问来源于stack exchange,提问作者pondermatic
相关产品推荐
相关产品推荐

