You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将联合类型传入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 02:18:14