无泛型下TypeScript实现参数与返回类型的关联映射
解决方案:用函数重载 + 类型守卫实现严格类型对应
既然你需要严格绑定Question/Response/Result的对应关系,同时还要保留完整的类型信息,函数重载绝对是最适合的方案——它能让TypeScript精准推断出返回值的类型,完全满足你的需求。
先给你完整的代码实现,再一步步解释:
首先我们先把基础接口和子类接口补全(给每个Question子类加个type字面量字段,方便后续做类型守卫,当然你也可以用其他特征来区分类型):
// 基础接口定义 interface Question {} interface Response {} interface Result {} // 具体的问题类型 interface MultipleChoiceQuestion extends Question { type: 'multiple-choice'; options: string[]; } interface DropdownQuestion extends Question { type: 'dropdown'; options: string[]; } interface InputQuestion extends Question { type: 'input'; placeholder: string; } // 对应的响应类型 interface MultipleChoiceResponse extends Response { selectedOption: string; } interface DropdownResponse extends Response { selectedOption: string; } interface InputResponse extends Response { inputValue: string; } // 对应的结果类型 interface MultipleChoiceResult extends Result { questionType: 'multiple-choice'; selected: string; } interface DropdownResult extends Result { questionType: 'dropdown'; selected: string; } interface InputResult extends Result { questionType: 'input'; input: string; }
接下来是核心的函数实现,我们用函数重载来定义三种合法的输入输出组合:
// 函数重载:明确每种Question+Response组合对应的Result类型 function createResult(question: MultipleChoiceQuestion, response: MultipleChoiceResponse): MultipleChoiceResult; function createResult(question: DropdownQuestion, response: DropdownResponse): DropdownResult; function createResult(question: InputQuestion, response: InputResponse): InputResult; // 函数的实际实现(用宽泛的类型接收参数,内部做类型判断) function createResult(question: Question, response: Response): Result { // 用类型守卫判断问题类型,TS会自动缩小类型范围 if ('type' in question && question.type === 'multiple-choice') { // 这里TS能推断response应该是MultipleChoiceResponse,放心访问属性 return { questionType: 'multiple-choice', selected: (response as MultipleChoiceResponse).selectedOption }; } if ('type' in question && question.type === 'dropdown') { return { questionType: 'dropdown', selected: (response as DropdownResponse).selectedOption }; } if ('type' in question && question.type === 'input') { return { questionType: 'input', input: (response as InputResponse).inputValue }; } // 处理非法输入的情况 throw new Error('Unsupported question or response type'); }
为什么这个方案好用?
- 类型安全:调用函数时,如果传入不匹配的Question和Response(比如把
InputQuestion和MultipleChoiceResponse一起传),TypeScript会直接报错,提前拦截错误。 - 类型推断准确:当你传入合法的组合时,TS会自动推断返回值的具体类型,比如:
const mcQ: MultipleChoiceQuestion = { type: 'multiple-choice', options: ['A', 'B'] }; const mcR: MultipleChoiceResponse = { selectedOption: 'A' }; const mcResult = createResult(mcQ, mcR); // mcResult的类型是MultipleChoiceResult,TS会提示它的questionType和selected字段 - 不需要泛型:完全符合你最初的限制(如果是因为某些原因不能用泛型的话)。
额外优化:自定义类型守卫
如果你的Question接口没有type这样的标识字段,也可以写自定义类型守卫函数来区分类型,比如:
function isMultipleChoiceQuestion(q: Question): q is MultipleChoiceQuestion { return 'options' in q && Array.isArray((q as MultipleChoiceQuestion).options); } // 然后在函数里用这个守卫: if (isMultipleChoiceQuestion(question)) { // 这里question的类型已经被缩小为MultipleChoiceQuestion return { questionType: 'multiple-choice', selected: (response as MultipleChoiceResponse).selectedOption }; }
备选方案(如果允许用泛型)
要是你后来发现可以用泛型了,也可以用映射类型来简化代码,把类型关联逻辑集中起来:
// 定义类型映射关系 type QuestionType = 'multiple-choice' | 'dropdown' | 'input'; type QuestionMap = { 'multiple-choice': MultipleChoiceQuestion; 'dropdown': DropdownQuestion; 'input': InputQuestion; }; type ResponseMap = { 'multiple-choice': MultipleChoiceResponse; 'dropdown': DropdownResponse; 'input': InputResponse; }; type ResultMap = { 'multiple-choice': MultipleChoiceResult; 'dropdown': DropdownResult; 'input': InputResult; }; function createResult<T extends QuestionType>( question: QuestionMap[T], response: ResponseMap[T] ): ResultMap[T] { if (question.type === 'multiple-choice') { return { questionType: 'multiple-choice', selected: response.selectedOption } as ResultMap[T]; } // 其他分支... throw new Error('Unsupported type'); }
这个方案更简洁,但依赖泛型,所以如果必须禁用泛型,还是回到函数重载的方案就好。
内容的提问来源于stack exchange,提问作者Mustafa Ekim
相关产品推荐
相关产品推荐

