能否编写以对象键为第二个参数的TypeScript类型谓词?
问题
假设API返回400错误时会抛出如下错误类型:
type ServerError = { status: string }; type ServerErrorResponse = { response: { errors: ServerError[]; }; };
为处理该响应,原本编写了两个结构相似的类型谓词函数:
function hasResponseKey(res: unknown): res is { response: unknown } { return typeof res === 'object' && res !== null && 'response' in res; } function hasErrorsKey(response: unknown): response is { errors: unknown } { return typeof response === 'object' && response !== null && 'errors' in response; }
最终实现的校验函数如下:
function isResponseServerErrorResponse( res: unknown ): res is ServerErrorResponse { return ( hasResponseKey(res) && hasErrorsKey(res.response) && Array.isArray(res.response.errors) ); }
但上述方案存在代码重复,希望通过传入键参数实现通用的类型谓词函数,比如:
function hasKey(res: unknown, key: string): res is { key: unknown } { return typeof res === 'object' && res !== null && key in res; }
请问是否可以编写这样的、第二个参数为对象键的类型谓词?
回答
当然可以,但你写的版本存在类型推断问题——TypeScript无法将传入的字符串参数key和类型谓词里的键名关联起来,导致类型谓词中的{ key: unknown }里的key只是一个字面量字符串,而非你实际传入的键名。
要实现真正通用的带键参数的类型谓词,需要用泛型来约束键的类型,让TypeScript能正确推断出对象的结构:
function hasKey<K extends PropertyKey>(res: unknown, key: K): res is { [P in K]: unknown } { return typeof res === 'object' && res !== null && key in res; }
这里的K extends PropertyKey确保key是合法的对象键类型(字符串、数字或符号),而{ [P in K]: unknown }则表示返回的类型是包含指定键K、值为unknown的对象。
用这个通用函数重构原来的校验函数,代码会更简洁:
function isResponseServerErrorResponse(res: unknown): res is ServerErrorResponse { return ( hasKey(res, 'response') && hasKey(res.response, 'errors') && Array.isArray(res.response.errors) ); }
这样既消除了代码重复,又能让TypeScript正确进行类型推断——当hasKey(res, 'response')返回true时,TypeScript会自动将res的类型推断为包含response键的对象,后续访问res.response也不会有类型错误。
如果你想进一步约束值的类型,还可以扩展这个泛型函数,比如允许传入值的类型校验:
function hasKeyWithType<K extends PropertyKey, T>( res: unknown, key: K, typeCheck: (val: unknown) => val is T ): res is { [P in K]: T } { return typeof res === 'object' && res !== null && key in res && typeCheck((res as { [P in K]: unknown })[key]); }
比如用它来直接校验errors是数组:
function isResponseServerErrorResponse(res: unknown): res is ServerErrorResponse { return ( hasKey(res, 'response') && hasKeyWithType(res.response, 'errors', Array.isArray) ); }
内容的提问来源于stack exchange,提问作者canecse
相关产品推荐
相关产品推荐

