如何从泛型可区分联合中根据请求类型提取响应类型?
根据请求类型匹配对应响应类型的问题与解决方案
需要建立请求与响应类型的关联,仅根据请求类型从请求/响应对中提取对应的响应类型。当前handleRequest函数中使用Extract类型无法正确收窄泛型类型,导致返回值出现类型不匹配报错。
原类型定义
interface AbstractWorkerRequest { id: string type: string } interface AbstractWorkerResponse { id: string type: string } type WorkerExchange<REQUEST extends AbstractWorkerRequest, RESPONSE extends AbstractWorkerResponse> = { type: REQUEST['type'] response: RESPONSE request: REQUEST } // 定义请求/响应对 type InitRequest = AbstractWorkerRequest & { type: 'init' body: number } type InitResponse = AbstractWorkerResponse & { type: 'init' body: { id: number } } type InitRequest2 = AbstractWorkerRequest & { type: 'init2' body: number } type InitResponse2 = AbstractWorkerResponse & { type: 'init2' body: { test: number } } type Exchanges = WorkerExchange<InitRequest, InitResponse> | WorkerExchange<InitRequest2, InitResponse2> type AnyRequest = Exchanges['request'] type AnyResponse = Exchanges['response']
原尝试代码(存在报错)
type TypeOfResponse<T extends AnyRequest> = Extract<AnyResponse, { type: T['type'] }> // 此处无法正确收窄类型 function clientCode() { const request: InitRequest = { id: '', type: 'init', body: -1 } const result = createPromise(request)// result应属于InitResponse类型 console.log(result.body.id) } function createPromise<T extends AnyRequest>(request: T): TypeOfResponse<T> { const result = handleRequest(request) return result } function handleRequest<T extends AnyRequest>(request: T): TypeOfResponse<T> { switch(request.type) { case "init": { const results: TypeOfResponse<InitRequest> = { id: '', type: 'init', body: { id: -1 }, } return results // 报错:Type 'InitResponse' is not assignable to type 'Extract<InitResponse2, { type: T["type"]; }>' } case 'init2': { const results: InitResponse2 = { id: '', type: 'init2', body: { test: -1 } } return results // 报错:Type 'InitResponse2' is not assignable to type 'Extract<InitResponse2, { type: T["type"]; }>'. } } }
解决方案(不修改createPromise和clientCode)
1. 修正TypeOfResponse类型定义
通过Exchanges联合类型直接关联请求与响应,替代原有的Extract写法,让TypeScript能准确推断对应关系:
// 方式一:通过Extract关联请求对应的Exchange,再取response类型 type TypeOfResponse<T extends AnyRequest> = Extract<Exchanges, { request: T }>['response'] // 方式二:先创建请求类型到响应类型的映射表,再通过类型索引 // type ResponseMap = { [E in Exchanges as E['type']]: E['response'] } // type TypeOfResponse<T extends AnyRequest> = ResponseMap[T['type']]
2. 调整handleRequest函数的类型推断
为了让switch分支的返回值能被正确识别,使用类型断言辅助推断:
function handleRequest<T extends AnyRequest>(request: T): TypeOfResponse<T> { switch(request.type) { case "init": { const results: InitResponse = { id: '', type: 'init', body: { id: -1 }, } return results as TypeOfResponse<T> } case 'init2': { const results: InitResponse2 = { id: '', type: 'init2', body: { test: -1 } } return results as TypeOfResponse<T> } default: throw new Error(`Unknown request type: ${request.type}`) } }
解释
原Extract<AnyResponse, { type: T['type'] }>的写法在泛型T未被具体实例化时,TypeScript无法确定T['type']对应的具体响应类型,导致返回值类型无法收窄。而通过Exchanges联合类型直接关联请求和响应的方式,能让TypeScript精准定位到当前请求对应的响应类型,解决类型不匹配的问题。
内容的提问来源于stack exchange,提问作者xKito
相关产品推荐
相关产品推荐

