我正在升级一些旧的TypeScript代码以使用最新的编译器版本,我在调用setTimeout时有麻烦。代码期望调用浏览器的setTimeout函数,该函数返回一个数字:
setTimeout(处理程序:(…Args: any[]) => void, timeout: number): number;
但是,编译器将此解析为节点实现,该实现将返回NodeJS。定时器:
setTimeout(回调:(…Args: any[]) => void, ms: number,…args: any[]): NodeJS.Timer;
这段代码没有在节点中运行,但是节点类型被拉入作为对其他东西的依赖(不确定是什么)。
如何指示编译器选择我想要的setTimeout版本?
下面是问题代码:
let n: number;
n = setTimeout(function () { /* snip */ }, 500);
这将产生编译器错误:TS2322:类型'Timer'不能分配给类型'number'。
2021年更新
Akxe的答案是Typescript 2.3中引入的ReturnType<Type>技术:
let n: ReturnType<typeof setTimeout>;
n = setTimeout(cb, 500);
这很好,似乎比显式强制转换更受欢迎。但在本例中,“n”的结果类型是“NodeJS”。Timeout”,可以这样使用:
let n: NodeJS.Timeout;
n = setTimeout(cb, 500);
ReturnType/NodeJS的唯一问题。超时方法是,特定于浏览器的环境中的数值操作仍然需要强制转换:
if ((n as unknown as number) % 2 === 0) {
clearTimeout(n);
}
原来的答案
一个不影响变量声明的解决方法:
let n: number;
n = setTimeout(function () { /* snip */ }, 500) as unknown as number;
此外,在特定于浏览器的环境中,可以使用没有强制转换的窗口对象:
let n: number;
n = window.setTimeout(function () { /* snip */ }, 500);
我还想提一下NodeJS的规范。超时包括[Symbol.toPrimitive](): number:
interface Timeout extends Timer {
/**
* If true, the `Timeout` object will keep the Node.js event loop active.
* @since v11.0.0
*/
hasRef(): boolean;
/**
* Sets the timer's start time to the current time, and reschedules the timer to
* call its callback at the previously specified duration adjusted to the current
* time. This is useful for refreshing a timer without allocating a new
* JavaScript object.
*
* Using this on a timer that has already called its callback will reactivate the
* timer.
* @since v10.2.0
* @return a reference to `timeout`
*/
refresh(): this;
[Symbol.toPrimitive](): number;
}
为了兼容性,Node中的其他超时api可以很好地处理普通整数id,它们不需要接受对象。这些对象用于“服务器”端,以允许对保持进程活跃和垃圾收集之类的事情进行更精细的控制。例如:
function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void;
这意味着你可以对setTimeout和setInterval的结果使用原语强制转换:
let timeoutId: number | undefined;
timeoutId = Number(setTimeout(callback, ms));
function clear() {
clearTimeout(timeoutId);
}
与任何API都不冲突,同时如果您需要依赖它作为其他API契约的原始值,也不会给您带来类型问题。