TypeScript -使用正确版本的setTimeout(节点vs窗口)

我正在升级一些旧的TypeScript代码以使用最新的编译器版本,我在调用setTimeout时遇到了麻烦。该代码期望调用浏览器的setTimeout函数,该函数返回一个数字:

setTimeout(handler: (...args: any[]) => void, timeout: number): number;

但是,编译器将此解析为节点实现,该实现将返回NodeJS。定时器:

setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;

这段代码没有在节点中运行,但是节点类型被拉入作为对其他东西的依赖(不确定是什么)。

如何指示编译器选择我想要的setTimeout版本?

下面是问题代码:

let n: number;
n = setTimeout(function () { /* snip */  }, 500);

这会产生编译器错误:

TS2322:类型'Timer'不能分配给类型'number'。

232342 次浏览

我遇到了同样的问题,我们的团队决定使用的解决方法是使用“any”作为计时器类型。例如:

let n: any;
n = setTimeout(function () { /* snip */  }, 500);

它将与setTimeout/setInterval/clearTimeout/clearInterval方法的两种实现一起工作。

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;

同样,在特定于浏览器的环境中,可以使用没有强制转换的window对象:

let n: number;
n = window.setTimeout(function () { /* snip */  }, 500);
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });


clearTimeout(timer);

通过使用ReturnType<fn>,你可以从平台中获得独立性。如果你在nodeJS服务器上运行代码,你不会被迫使用anywindow.setTimeout,因为会中断。服务器端呈现的页面)。


好消息是,这也兼容Deno!

我想这取决于您将在哪里运行代码。

如果你的运行时目标是服务器端Node JS,使用:

let timeout: NodeJS.Timeout;
global.clearTimeout(timeout);

如果你的运行时目标是浏览器,使用:

let timeout: number;
window.clearTimeout(timeout);

这可能适用于旧版本,但对于TypeScript版本^3.5.3和Node.js版本^10.15.3,你应该能够从计时器模块导入特定于node的函数,即:

import { setTimeout } from 'timers';

这将返回类型为NodeJS.Timeout超时实例,您可以将其传递给clearTimeout:

import { clearTimeout, setTimeout } from 'timers';


const timeout: NodeJS.Timeout = setTimeout(function () { /* snip */  }, 500);


clearTimeout(timeout);

如果你的目标是windowsetInterval。那么你也可以写成

let timerId: number = setInterval((()=>{
this.populateGrid(true)
}) as TimerHandler, 5*1000)
}

我通过设置解决了这个问题

tsconfig.json:
{
"compilerOptions": {
"skipLibCheck": true,
}
}

然后创建。d.ts

* .d.ts:
declare namespace NodeJS {
type Timeout = number;
type Timer = number;
}

Typescript版本4.2.3

我在使用React时也遇到了类似的问题,解决方法如下:

import React, { useRef, useState, useEffect} from 'react';
import { Alert } from '../types/alerts';


const AlertComponent: React.FC<{alert: Alert}> = ({alert}) => {
const intervalRef = useRef<NodeJS.Timeout>();
const [count, setCount] = useState(alert.timeLimit)


useEffect(() => {
intervalRef.current = setInterval(
() => {setCount((count) => count - 1)},
1000
)


return () => {
clearInterval(intervalRef.current as NodeJS.Timeout)
}
}, [])


return (
<p>{count}</p>
)
}


export default AlertComponent;

在我的useEffect()钩子中,我有clearInterval(intervalRef.current as NodeJS.Timeout),因为clearInterval显式地寻找NodeJS。Timeout |未定义,所以我必须去掉未定义的部分。

这对我来说非常有效。

type Timer = ReturnType<typeof setTimeout>


const timer: Timer = setTimeout(() => {}, 1000)

我正在使用RTL测试我的Counter应用程序,特别是在测试一个元素,如果计数达到15就要删除。由于组件在运行测试后被销毁,setTimeout仍然会在此之后运行,并抛出一个错误,说React不能对卸载的组件执行状态更新。因此,基于dhilt的回答,我能够以这种方式修复useEffect清理函数:

const [count, setCount] = useState(initialCount);
const [bigSize, setBigSize] = useState(initialCount >= 15);


useEffect(() => {
let id: NodeJS.Timeout;


if(count >= 15) {
id = setTimeout(() => setBigSize(true), 300);
}


return function cleanup() {
clearTimeout(id);
}
});

这是测试套件:

describe('when the incrementor changes to 5 and "add" button is clicked', () => {
beforeEach(async () => {
userEvent.type(screen.getByLabelText(/Incrementor/), '{selectall}5');
userEvent.click(screen.getByRole('button', {name: "Add to Counter"}));
await screen.findByText('Current Count: 15');
})
            

it('renders Current Count: 15', () => {
expect(screen.getByText('Current Count: 15')).toBeInTheDocument();
});
        

it('renders too big and will dissapear after 300ms',async() => {
await waitForElementToBeRemoved(() => screen.queryByText(/size: small/i))
});
        

})

还想提一下,NodeJS.Timeout的规范包括[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;

这意味着你可以对setTimeoutsetInterval的结果使用原始类型强制转换:

let timeoutId: number | undefined;
timeoutId = Number(setTimeout(callback, ms));


function clear() {
clearTimeout(timeoutId);
}

与任何API都不冲突,同时如果您需要依赖它作为其他API契约的原始值,也不会给您带来类型问题。

TS2322:类型'Timer'不能分配给类型'number'。

简单的解决方案

ABC:任何;

对于assgin

abc = setInterval或abc = setTimeout