TypeScript中如何实现同名的导出与非导出函数?
这个问题的核心在于TypeScript静态类型检查和JavaScript运行时行为的差异:JavaScript里函数声明会提升到所在函数作用域的顶部,所以你的代码编译后能正常运行;但TypeScript会把namespace内部的函数声明和变量声明视为同一作用域下的标识符,因此抛出重复定义错误。
不用// @ts-ignore的话,有几种更符合TypeScript编码风格的解决方案:
方案一:用IIFE隔离内部函数作用域
这是最贴近你需求的方案——既保留内部函数与导出变量同名的语义,又通过局部作用域避免标识符冲突:
namespace MyGreatNamespace { function externallyCalled<T extends (...args: any[]) => Promise<void>>(f: T): T { return <T>async function externallyCalled(...args) { try { // setup(); return await f.apply(this, args); } finally { // cleanup(); } }; } // 用IIFE创建局部作用域,内部的DoSomething仅在此作用域可见 export const DoSomething = (() => { function DoSomething() {} return externallyCalled(DoSomething); })(); }
原理是:IIFE内部的DoSomething函数属于局部作用域,不会和外部namespace中的导出变量DoSomething产生命名冲突,TypeScript的类型检查会正常通过,同时编译后的JS行为和你预期的一致。
方案二:给内部函数加前缀区分命名
如果觉得IIFE有点繁琐,也可以给内部函数加一个约定俗成的前缀(比如下划线),用函数表达式定义后再包装导出:
namespace MyGreatNamespace { function externallyCalled<T extends (...args: any[]) => Promise<void>>(f: T): T { return <T>async function externallyCalled(...args) { try { // setup(); return await f.apply(this, args); } finally { // cleanup(); } }; } // 内部函数用下划线前缀区分,避免命名冲突 const _DoSomething = function() {}; export const DoSomething = externallyCalled(_DoSomething); }
这种写法更简洁,虽然内部函数和导出变量名字不同,但通过前缀约定能清晰表达两者的关联,同样符合TypeScript的编码规范。
为什么原写法会报错?
在TypeScript的检查逻辑中,namespace内部的function DoSomething()声明会被提升到当前作用域的顶部,所以当你写export const DoSomething = externallyCalled(DoSomething);时,TypeScript认为DoSomething已经被声明为一个const变量,后面的函数声明又重复使用了这个标识符,因此抛出Duplicate identifier错误。而JavaScript运行时,函数声明会被提升到IIFE的顶部,实际执行顺序是先定义内部函数,再赋值给导出变量,所以能正常运行——这就是静态检查和运行时行为的差异导致的矛盾。
内容的提问来源于stack exchange,提问作者Fowl

