我正在升级一些旧的 TypeScript 代码以使用最新的编译器版本,但在调用 setTimeout
时遇到了问题。该代码期望调用浏览器的 setTimeout
函数,该函数返回一个数字:
setTimeout(handler: (...args: any[]) => void, timeout: number): number;
但是,编译器将其解析为节点实现,它返回一个 NodeJS.Timer:
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer;
此代码不在节点中运行,但节点类型作为对其他东西的依赖被拉入(不确定是什么)。
如何指示编译器选择我想要的 setTimeout
版本?
这是有问题的代码:
let n: number;
n = setTimeout(function () { /* snip */ }, 500);
这会产生编译器错误:
TS2322:“计时器”类型不可分配给“数字”类型。
types
不包括 node
但 setTimeout
仍然获得它的节点类型而不是它的浏览器类型。 types
默认为 node_modules/@types
中的所有类型,如 typescriptlang.org/tsconfig#types 中所述,但即使您确实指定 types
并且不包含 {8 },为什么 setTimeout
仍然得到它的 Node 类型,你怎么能得到浏览器类型? @Axke 的解决方案有点骇人听闻,基本上是说它返回它返回的内容。 TypeScript 可能仍然会找到错误的类型,但至少它会一直是错误的。
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });
clearTimeout(timer);
通过使用 ReturnType<fn>
,您可以独立于平台。如果您在 nodeJS 服务器(例如服务器端呈现的页面)上运行代码,您将不会被迫使用 any
或 window.setTimeout
,这 将 中断。
好消息,这也与 Deno 兼容!
2021 年更新
Akxe's answer 建议在 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.Timeout 方法的唯一问题是浏览器特定环境中的数字操作仍然需要强制转换:
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);
window.setTimeout
)应该是这个问题的正确答案,因为它是最清晰的解决方案。
any
类型,那么您实际上并没有给出 TypeScript 答案。
number
类型会导致 TypeScript 特定的 lint 错误,因为 setTimeout
函数需要的不止这些。
window.setTimeout
可能会导致单元测试框架 (node.js) 出现问题。最好的解决方案是使用 let n: NodeJS.Timeout
和 n = setTimeout
。
self
而不是 window
stackoverflow.com/questions/57172951/… 。希望正确设置打字稿会为其分配正确的类型,但我没有经验。
我想这取决于你将在哪里运行你的代码。
如果您的运行时目标是服务器端 Node JS,请使用:
let timeout: NodeJS.Timeout;
global.clearTimeout(timeout);
如果您的运行时目标是浏览器,请使用:
let timeout: number;
window.clearTimeout(timeout);
这可能适用于旧版本,但使用 TypeScript 版本 ^3.5.3
和 Node.js 版本 ^10.15.3
,您应该能够从 Timers 模块导入特定于节点的函数,即:
import { setTimeout } from 'timers';
这将返回您可以传递给 clearTimeout
的 NodeJS.Timeout
类型的 Timeout 实例:
import { clearTimeout, setTimeout } from 'timers';
const timeout: NodeJS.Timeout = setTimeout(function () { /* snip */ }, 500);
clearTimeout(timeout);
setTimeout
,类似 const { setTimeout } = window
的内容将清除这些错误。
这对我来说非常有效。
type Timer = ReturnType<typeof setTimeout>
const timer: Timer = setTimeout(() => {}, 1000)
如果您的目标是 window
中的 setInterval
。然后你也可以写成
let timerId: number = setInterval((()=>{
this.populateGrid(true)
}) as TimerHandler, 5*1000)
}
还想提一下 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;
这意味着您可以对 setTimeout
和 setInterval
的结果使用原始类型转换:
let timeoutId: number | undefined;
timeoutId = Number(setTimeout(callback, ms));
function clear() {
clearTimeout(timeoutId);
}
不与任何一个 API 冲突,如果您需要依赖它作为其他 API 合约的原始值,则不会让您在以后遇到类型问题。
我通过设置解决了这个问题
tsconfig.json:
{
"compilerOptions": {
"skipLibCheck": true,
}
}
并创建 .d.ts
*.d.ts:
declare namespace NodeJS {
type Timeout = number;
type Timer = number;
}
打字稿版本 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 |未定义,所以我不得不摆脱未定义的部分。
我正在使用 RTL 测试我的 Counter 应用程序,特别是在测试计数达到 15 时要删除的元素。由于组件在运行测试后被销毁,setTimeout 之后仍会运行并抛出错误,指出 React 无法执行未安装组件的状态更新。因此,基于 dhilt's answer,我能够以这种方式修复我的 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))
});
})
我遇到了同样的问题,我们团队决定使用的解决方法就是对计时器类型使用“任何”。例如:
let n: any;
n = setTimeout(function () { /* snip */ }, 500);
它适用于 setTimeout/setInterval/clearTimeout/clearInterval 方法的两种实现。
any
通常不是要走的路。
setTimeout
/clearTimeout
的平台提供了正确的类型定义并且不使用any
。setTimeout
,则返回类型为NodeJS.Timeout
;如果使用window.setTimeout
,则返回类型为number
。不需要使用ReturnType
。setTimeout
函数时需要它,并期望其结果存储在变量中。在 TS 游乐场自己尝试一下。