将联合类型传入Knex时返回any类型的问题咨询
问题原因及解决办法
为什么泛型标注返回any?
核心问题出在查询返回字段和UserRecord联合类型的结构不匹配:
UserRecord是两个分支的联合类型:一个分支包含providerId(当provider为GITLAB/GITHUB/GOOGLE时),另一个包含password(当provider为EMAIL时)。- 但你的
select语句只选取了password,没选providerId,导致实际返回的对象永远缺少providerId字段,无法匹配联合类型中的第一个分支。 - Knex的类型系统会校验泛型和实际返回字段的兼容性,当发现无法匹配时,就会把返回类型降级为
any,而非强制使用你指定的泛型。
而类型断言as UserRecord是手动跳过了TS的类型检查,所以能得到预期类型,但这其实不安全——因为实际返回的对象确实缺少providerId,不符合UserRecord的定义。
解决办法
1. 补充查询字段,匹配联合类型结构
修改select语句,把providerId也加入查询,这样不管provider是哪种类型,返回的对象都会包含所有必要字段(不存在的字段会返回null),Knex就能正确推导类型:
const user = await knex<UserRecord>('users') .select('id', 'name', 'provider', 'email', 'active', 'avatar', 'password', 'providerId') // 补充providerId .where('email', req.body.email) .first()
2. 创建适配查询结果的类型
如果确实不需要providerId字段,可以定义一个专门的查询返回类型,用它作为Knex的泛型:
type UserQueryResult = Omit<UserRecord, 'providerId'> & { providerId?: never } const user = await knex<UserQueryResult>('users') .select('id', 'name', 'provider', 'email', 'active', 'avatar', 'password') .where('email', req.body.email) .first()
3. 使用类型守卫收窄类型
查询后通过类型守卫确认返回值符合UserRecord结构,既安全又能得到正确类型:
const user = await knex('users') .select('id', 'name', 'provider', 'email', 'active', 'avatar', 'password') .where('email', req.body.email) .first() // 类型守卫函数 function isUserRecord(user: any): user is UserRecord { if (!user) return false if (user.provider === 'EMAIL') { return typeof user.password === 'string' } else { // 若查询未返回providerId,会返回false,避免不安全的类型断言 return typeof user.providerId === 'string' } } if (!isUserRecord(user)) { return res.status(400).json({ success: false, message: 'USER_NOT_FOUND' }) } // 此处user的类型已自动收窄为UserRecord
内容的提问来源于stack exchange,提问作者Trevor V
相关产品推荐
相关产品推荐

