TypeScript返回未知键对象数组时访问属性报错的解决方法
TypeScript迁移数据库查询函数的最佳实现方案
TypeScript 本身无法静态解析SQL语句的逻辑,自然没法自动推断查询返回对象的具体字段结构,这类场景下改造成本最低、实用性最强的方案是给查询函数增加泛型支持,由调用方在调用时指定单次查询的结果行类型,同时兼容存量未改造的JS代码。
改造查询函数类型定义
给原有query函数增加泛型参数,设置默认值为通用对象类型,保证老代码不需要立刻修改也不会触发类型报错,支持渐进式迁移:
/** * 对当前连接的数据库执行查询 * @param sql 待执行的SQL查询语句 * @param params 预处理语句对应的参数数组 * @returns 查询结果构成的对象数组,查询完成后resolve */ const query = async <T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]> => { // 原有数据库操作逻辑保持不变即可 }
调用时指定返回类型
在调用query的位置,传入本次查询对应的结果行类型,TypeScript就能正确识别返回对象的字段,不会抛出属性不存在的类型错误。以计数查询场景为例,改造后写法如下:
// 定义本次查询返回的行结构 type CountResult = { cnt: number } const result = await query<CountResult>('SELECT count(*) as cnt from Table where id=?', [2]) // 此处TS可正确推导result[0]上存在cnt字段,无类型报错 return result[0].cnt
迁移注意事项
- 泛型仅在编译阶段提供类型校验和提示,不会对运行时返回的数据做结构校验。如果对数据可靠性要求高,可以搭配轻量结构校验库,在查询结果返回后做格式校验,避免SQL字段名写错、查询逻辑变更导致的线上问题。
- 项目中高频复用的查询结果结构,可以抽成公共类型定义(比如用户表行类型
UserRow、计数结果类型CountResult),减少重复的类型声明代码。 - 存量代码不需要一次性改造完成,未传入泛型参数的调用点,返回值默认为
Record<string, unknown>类型,可以在后续迭代中逐步补全类型。
可选:带运行时校验的严谨实现
如果需要同时兼顾编译时类型提示和运行时数据安全,可以结合结构校验库做一层封装,示例写法如下:
import { z } from 'zod' // 同时定义运行时校验规则和TS类型 const CountResultSchema = z.object({ cnt: z.number() }) type CountResult = z.infer<typeof CountResultSchema> const result = await query<CountResult>('SELECT count(*) as cnt from Table where id=?', [2]) // 运行时校验数据结构,不符合预期直接抛错,同时自动推导TS类型 const validResult = CountResultSchema.parse(result[0]) return validResult.cnt
内容的提问来源于stack exchange,提问作者Steffen Nieuwenhoven
相关产品推荐
相关产品推荐

