typescript - 为什么 Typescript 不以正确的方式支持函数重载?
问题描述
关于 Typescript 中函数重载的工作方式有很多问题(例如,TypeScript 函数重载)。但不存在诸如“为什么它会以这种方式工作?”之类的问题。现在函数重载看起来像这样:
function foo(param1: number): void;
function foo(param1: number, param2: string): void;
function foo(...args: any[]): void {
if (args.length === 1 && typeof args[0] === 'number') {
// implementation 1
} else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
// implementation 2
} else {
// error: unknown signature
}
}
我的意思是,Typescript 的创建是为了让程序员的生活更轻松,它添加了一些所谓的“语法糖”,它提供了 OOD 的优势。那么为什么 Typescript 不能代替程序员来做这些烦人的事情呢?例如,它可能看起来像:
function foo(param1: number): void {
// implementation 1
};
function foo(param1: number, param2: string): void {
// implementation 2
};
foo(someNumber); // result 1
foo(someNumber, someString); // result 2
foo(someNumber, someNumber); // ts compiler error
此 Typescript 代码将被转换为以下 Javascript 代码:
function foo_1(param1) {
// implementation 1
};
function foo_2(param1, param2) {
// implementation 2
};
function foo(args) {
if (args.length === 1 && typeof args[0] === 'number') {
foo_1(args);
} else if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string') {
foo_2(args);
} else {
throw new Error('Invalid signature');
}
};
而且我没有找到任何原因,为什么 Typescript 不能这样工作。有任何想法吗?
解决方案
如果你愿意,思考如何在 TypeScript 中实现“真正的”函数重载是一个有趣的练习。让编译器获取一堆单独的函数并从中生成一个函数很容易。但是在运行时,这个单一函数必须根据参数的数量和类型知道要调用几个底层函数中的哪一个。参数的数量肯定可以在运行时确定,但是参数的类型是完全擦除的,所以没有办法实现它,你就卡住了。
当然,您可能会违反 TypeScript 的设计目标之一(特别是关于添加运行时类型信息的非目标 #5),但这不会发生。看起来很明显,当您检查时number
,您可以输出typeof xxx === 'number'
,但是当检查用户定义的时您会输出什么interface
?解决这个问题的一种方法是要求开发人员为每个函数重载提供一个用户定义的类型保护,以确定参数是否是正确的类型。但现在它是在让开发人员为每个函数重载指定事物对的领域,这比当前的 TypeScript 重载概念更复杂。
为了好玩,让我们看看作为一个期望函数和类型保护来构建重载函数的库,您自己可以有多接近这一点。像这样的东西怎么样(假设 TS 3.1 或更高版本):
interface FunctionAndGuard<A extends any[]=any[], R=any, A2 extends any[]= A> {
function: (...args: A) => R,
argumentsGuard: (args: any[]) => args is A2
};
type AsAcceptableFunctionsAndGuards<F extends FunctionAndGuard[]> = { [K in keyof F]:
F[K] extends FunctionAndGuard<infer A, infer R, infer A2> ?
FunctionAndGuard<A2, R, A> : never
}
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type Lookup<T, K> = K extends keyof T ? T[K] : never;
type FunctionAndGuardsToOverload<F extends FunctionAndGuard[]> =
Lookup<UnionToIntersection<F[number]>, 'function'>;
function makeOverloads<F extends FunctionAndGuard[]>(
...functionsAndGuards: F & AsAcceptableFunctionsAndGuards<F>
): FunctionAndGuardsToOverload<F> {
return ((...args: any[]) =>
functionsAndGuards.find(fg => fg.argumentsGuard(args))!.function(...args)) as any;
}
该makeOverloads()
函数接受可变数量的FunctionAndGuard
参数,并返回一个重载函数。试试看:
function foo_1(param1: number): void {
// implementation 1
};
function foo_2(param1: number, param2: string): void {
// implementation 2
};
const foo = makeOverloads({
function: foo_1,
argumentsGuard: (args: any[]): args is [number] =>
args.length === 1 && typeof args[0] === 'number'
}, {
function: foo_2,
argumentsGuard: (args: any[]): args is [number, string] =>
args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'string'
}
);
foo(1); // okay
foo(1, "two"); // okay
foo(1, 2); // error
有用。耶?
回顾一下:在运行时没有某种方式来确定参数的类型是不可能的,这在一般情况下需要开发人员指定的类型保护。因此,您可以通过要求开发人员为每个重载提供类型保护来进行重载,或者通过执行他们现在所做的,通过具有单个实现和多个调用签名来进行重载。后者更简单。
希望能提供一些见解。祝你好运!
推荐阅读
- svn - SVN 默默地忽略忽略模式中未包含的某些文件 (*.reg)
- apache-kafka - Kafka 在同一台机器上流式传输应用程序
- python - 获得 3 个不同数字之和的整数/舍入问题
- security - 浏览器突然要求我使用大多数网站的用户名和密码登录
- python - 减去日期时间以获得以月为单位的增量
- android - 任务 ':app:processDebugResources' AAPT 执行失败:错误:找不到资源 android:attr/lStar。[反应原生]
- sql - 用@或不使用变量声明游标的区别
- python - KivyMD 窗口未显示应用程序的图标。在其他类似问题上尝试了所有其他方式
- webpack - PostCSS 无法使用变量选择器编译 SASS
- javascript - 在Javascript中结合while循环和onclick函数