如何限制函数返回值为void,阻止不符合()=>void签名的函数赋值?
() => void类型 这个问题确实戳中了TypeScript里一个容易让人困惑的设计点——为什么明明bar被声明成() => void,却能接受有返回值的foo?我来一步步给你拆解并解决这个问题:
为什么默认允许这种赋值?
TypeScript的类型系统里,() => void是故意兼容有返回值的函数的。这是因为在绝大多数场景下,当我们调用一个声明为返回void的函数时,根本不会去使用它的返回值——比如Array.forEach的回调函数,你返回字符串、数字甚至对象都不会影响代码逻辑,所以TypeScript为了便利性做了这个设计。但如果你的场景(比如调用某个绝不应该接收有返回值函数的JS API)必须严格确保函数无返回值,就需要额外的约束手段。
严格限制的解决方案
1. 自定义工具类型强制校验返回值
你可以定义一个工具类型,专门用来筛选出真正返回void的函数,把有返回值的函数排除在外:
// 工具类型:只有当传入的函数返回值严格为void时,才返回原类型,否则返回never type StrictVoidFunction<T> = T extends () => infer R ? R extends void ? T : never : never; // 重新声明你的JS API,用StrictVoidFunction约束参数 declare let jsApiThatShouldNeverAcceptTypeofFoo: (bar: StrictVoidFunction<() => void>) => void; let foo = () => 'foo'; let bar: () => void = foo; // 这里还是会通过(因为默认的void宽松规则) jsApiThatShouldNeverAcceptTypeofFoo(bar); // 这里会报错!类型不兼容
这样一来,当你试图把bar(本质是() => string)传给这个API时,TypeScript就会抛出类型错误,达到你想要的严格校验效果。
2. 直接在函数参数里做类型收窄
如果你不想单独定义工具类型,也可以直接在API的参数类型里写收窄逻辑:
declare let jsApiThatShouldNeverAcceptTypeofFoo: <T extends () => any>( bar: T extends () => void ? T : never ) => void; let foo = () => 'foo'; jsApiThatShouldNeverAcceptTypeofFoo(foo); // 报错:类型'() => string'不能赋值给类型'never'
这种方式更紧凑,同样能阻止有返回值的函数被传入。
注意:--strictFunctionTypes帮不了你
很多人会想到开启严格函数类型检查,但要注意:这个选项主要是解决函数参数的双向协变问题,不会改变() => void兼容有返回值函数的行为——这是TypeScript特意保留的规则,所以靠编译选项无法解决这个问题。
总结
TypeScript默认的() => void是宽松的,目的是提升日常开发的便利性;但如果你的场景需要绝对严格的无返回值校验,就需要通过自定义工具类型或者直接在参数里做类型收窄,来排除所有有返回值的函数。
内容的提问来源于stack exchange,提问作者Estus Flask

