TypeScript根据运行时值适配多类型函数签名问题求解
问题背景
我在工厂对象中实现了名为onSubmit的方法,该工厂会根据特定运行时值创建对应方法、访问匹配属性,核心代码逻辑如下:
const formByValue = setupFormByValue(value);
const setupFormByValue = (value: "x" | "y") => { switch (value) { case "x": return xConstruct(); default: return yConstruct(); } };
// 不同分支的属性类型定义 type Values = { x: { testX: string }; y: { testY: string } }; // 提交参数类型 type SubmitProps<T extends "x" | "y"> = { values: Values[T]; }; const xConstruct = () => { return { onSubmit: async (props: SubmitProps<"x">) => { /// 访问 props.testX } } } const yConstruct = () => { return { onSubmit: async (props: SubmitProps<"y">) => { /// 访问 props.testY } } }
调用formByValue.onSubmit({ values })时,TypeScript抛出类型错误,提示values存在不兼容的类型签名,原因是"x"、"y"两个分支对应的values类型不匹配。
报错示例截图:
需求:在保留运行时分支逻辑的前提下,让x、y两个分支的函数签名与类型正确匹配,消除类型校验报错。
报错根源
原实现中setupFormByValue没有做泛型类型关联,TS会将其返回值推断为两个分支返回类型的联合类型。当调用联合类型上的函数时,TS会将参数类型推导为所有分支函数参数类型的交叉类型,也就是要求传入的values同时满足{testX: string}和{testY: string},和实际单分支的参数结构冲突,因此抛出类型错误。
解决方案
分两步改造,既保留运行时分支逻辑,又能获得正确的类型校验:
- 给工厂函数加泛型,建立入参value和返回实例类型的关联,避免无意义的联合类型推断
- 给不同分支返回的实例加判别式属性,支持动态value场景下的类型收窄
完整改造代码如下:
type Values = { x: { testX: string }; y: { testY: string } }; type SubmitProps<T extends "x" | "y"> = { values: Values[T]; }; // x分支实例加判别属性type const xConstruct = () => { return { type: "x" as const, onSubmit: async (props: SubmitProps<"x">) => { console.log(props.values.testX) } } } // y分支实例加判别属性type const yConstruct = () => { return { type: "y" as const, onSubmit: async (props: SubmitProps<"y">) => { console.log(props.values.testY) } } } // 定义实例联合类型 type FormInstance = ReturnType<typeof xConstruct> | ReturnType<typeof yConstruct>; // 工厂函数加泛型,通过条件类型匹配入参和返回值类型 const setupFormByValue = <T extends "x" | "y">(value: T): Extract<FormInstance, {type: T}> => { switch (value) { case "x": return xConstruct() as Extract<FormInstance, {type: T}>; default: return yConstruct() as Extract<FormInstance, {type: T}>; } };
使用方式
- 传入字面量value时,TS会自动推断对应实例类型,直接传参即可通过校验:
// 自动推断为x分支实例类型 const formX = setupFormByValue("x"); await formX.onSubmit({values: {testX: "x值"}}); // 无报错 // 自动推断为y分支实例类型 const formY = setupFormByValue("y"); await formY.onSubmit({values: {testY: "y值"}}); // 无报错
- 传入动态运行时value时,通过判别属性
type收窄类型后再调用:
// 模拟运行时动态获取的value const dynamicValue = (Math.random() > 0.5 ? "x" : "y") as "x" | "y"; const dynamicForm = setupFormByValue(dynamicValue); if (dynamicForm.type === "x") { // TS自动收窄为x分支实例 await dynamicForm.onSubmit({values: {testX: "动态x值"}}); } else { // TS自动收窄为y分支实例 await dynamicForm.onSubmit({values: {testY: "动态y值"}}); }
内容的提问来源于stack exchange,提问作者Otavio Augusto
相关产品推荐
相关产品推荐

