TypeScript:无需判别联合特殊字段实现多变体过滤类型的方案
TypeScript联合类型查询参数的类型检查优化方案
问题场景与报错
遇到的TypeScript报错:
Property 'id' does not exist on type 'Filter'. Property 'id' does not exist on type '{ slug: string; language: { slug: string; }; }'
对应代码:
// filter record by id, or by slug + language type Filter = { id: string } | { slug: string; language: { slug: string } } function query(filter: Filter) { if (filter.id) { console.log('query by id') } else { console.log('query by slug + language.slug') } }
需求说明:需要定义5种以上的查询参数变体,不想用带type这类特殊判别字段的联合类型(避免命名繁琐、维护性差),也不想为每种过滤类型单独编写方法,希望找到更优的类型检查方案。
最优解决方案
1. 使用in操作符原生类型守卫
这是最简洁直接的方案,TypeScript原生支持通过in操作符检查属性存在性,自动收窄联合类型:
type Filter = { id: string } | { slug: string; language: { slug: string } } function query(filter: Filter) { if ('id' in filter) { console.log('query by id', filter.id) // 类型自动收窄为 { id: string } } else { console.log('query by slug + language.slug', filter.slug, filter.language.slug) // 类型自动收窄为另一种变体 } }
优势:无需额外字段,代码简洁,符合直觉,完全满足基础的互斥属性联合类型场景。
2. 自定义类型守卫函数(适合多变体/复杂检查场景)
当变体数量超过5种,或需要更严谨的类型校验(比如同时验证属性类型),可以抽离自定义类型守卫函数,提升代码可维护性:
// 扩展为多种查询变体 type Filter = | { id: string } | { slug: string; language: { slug: string } } | { email: string } | { phone: string } | { username: string; platform: string } // 自定义类型守卫:检查是否为ID查询 function isIdFilter(filter: Filter): filter is { id: string } { return 'id' in filter && typeof filter.id === 'string' } // 自定义类型守卫:检查是否为slug+语言查询 function isSlugLangFilter(filter: Filter): filter is { slug: string; language: { slug: string } } { return 'slug' in filter && 'language' in filter && typeof filter.slug === 'string' && typeof filter.language.slug === 'string' } // 其他类型守卫可按需添加... function query(filter: Filter) { if (isIdFilter(filter)) { console.log('query by id', filter.id) } else if (isSlugLangFilter(filter)) { console.log('query by slug + language.slug', filter.slug, filter.language.slug) } else if ('email' in filter) { console.log('query by email', filter.email) } else if ('phone' in filter) { console.log('query by phone', filter.phone) } else { console.log('query by username + platform', filter.username, filter.platform) } }
优势:把复杂的类型检查逻辑封装成函数,代码更整洁,复用性强;同时可以做更严谨的校验,避免属性存在但类型不符的情况。
原思路问题说明
原代码直接访问filter.id时,TypeScript无法确定当前传入的filter是联合类型中的哪一个成员——因为第二个变体没有id属性,所以触发类型报错。而带判别字段的联合类型虽然可行,但需要为每个变体定义唯一标识,变体越多,命名成本越高,且会增加冗余字段,不利于复杂过滤场景的维护。
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

