ChatGPT解决这个技术问题 Extra ChatGPT

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

我正在升级一些旧的 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:“计时器”类型不可分配给“数字”类型。

您的 tsconfig.json 中有 types:["node"] 吗?请参阅stackoverflow.com/questions/42940954/…
@koe 不,我没有 types:["node"] tsconfig 文件中的选项。但是节点类型作为 npm 依赖项被引入到其他东西中。
您还可以在 tsconfig.json 中显式定义“类型” - 当您省略“节点”时,它不会在编译中使用。例如“类型”:[“jQuery”]
令人惊讶的是,@koe(使用“类型”选项)的答案没有任何投票,这是唯一真正正确的答案。
@KevinTighe 的 types 不包括 nodesetTimeout 仍然获得它的节点类型而不是它的浏览器类型。 types 默认为 node_modules/@types 中的所有类型,如 typescriptlang.org/tsconfig#types 中所述,但即使您确实指定 types 并且包含 {8 },为什么 setTimeout 仍然得到它的 Node 类型,你怎么能得到浏览器类型? @Axke 的解决方案有点骇人听闻,基本上是说它返回它返回的内容。 TypeScript 可能仍然会找到错误的类型,但至少它会一直是错误的。

m
mlntdrv
let timer: ReturnType<typeof setTimeout> = setTimeout(() => { ... });

clearTimeout(timer);

通过使用 ReturnType<fn>,您可以独立于平台。如果您在 nodeJS 服务器(例如服务器端呈现的页面)上运行代码,您将不会被迫使用 anywindow.setTimeout,这 中断。

好消息,这也与 Deno 兼容!


我的理解是这是正确的答案,应该被接受,因为它为每个支持 setTimeout / clearTimeout 的平台提供了正确的类型定义并且不使用 any
如果您正在编写一个在 NodeJS 和浏览器上运行的库,这就是解决方案。
如果直接使用 setTimeout,则返回类型为 NodeJS.Timeout;如果使用 window.setTimeout,则返回类型为 number。不需要使用 ReturnType
@cchamberlain 您在运行 setTimeout 函数时需要它,并期望其结果存储在变量中。在 TS 游乐场自己尝试一下。
这个解决方案对我来说是正确的。如果不使用它,我的节点应用程序可以使用 TS 正确编译,但是在使用 Jest 单元测试时,它选择了不正确的 window.setTimeout 定义
d
dhilt

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.Timeoutn = setTimeout
@AntonOfTheWoods 您应该可以再次对其进行范围限定,但使用 self 而不是 window stackoverflow.com/questions/57172951/… 。希望正确设置打字稿会为其分配正确的类型,但我没有经验。
c
cwouter

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

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

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

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

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

N
Nick Bernard

这可能适用于旧版本,但使用 TypeScript 版本 ^3.5.3 和 Node.js 版本 ^10.15.3,您应该能够从 Timers 模块导入特定于节点的函数,即:

import { setTimeout } from 'timers';

这将返回您可以传递给 clearTimeoutNodeJS.Timeout 类型的 Timeout 实例:

import { clearTimeout, setTimeout } from 'timers';

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

clearTimeout(timeout);

同样,如果您想要浏览器版本的 setTimeout,类似 const { setTimeout } = window 的内容将清除这些错误。
D
DedaDev

这对我来说非常有效。

type Timer = ReturnType<typeof setTimeout>

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

K
Krishna

如果您的目标是 window 中的 setInterval。然后你也可以写成

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

M
M Miller

还想提一下 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 合约的原始值,则不会让您在以后遇到类型问题。


n
n9512378

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

tsconfig.json:

{
  "compilerOptions": {
    "skipLibCheck": true,
  }
}

并创建 .d.ts

*.d.ts:

declare namespace NodeJS {
    type Timeout = number;
    type Timer = number;
}

打字稿版本 4.2.3


D
Dharman

我正在使用 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 |未定义,所以我不得不摆脱未定义的部分。


D
Danniel Hansel

我正在使用 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))
        });
        
    })

M
Mark Dolbyrev

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

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

它适用于 setTimeout/setInterval/clearTimeout/clearInterval 方法的两种实现。


是的,这确实有效。我还意识到我可以直接在窗口对象上指定方法:window.setTimeout(...)。不确定这是否是最好的方法,但我现在会坚持下去。
您可以在 typescript 中正确导入 NodeJS 命名空间,请参阅 this answer
使用 any 通常不是要走的路。
是的,但正确输入和工作是最好的。
@avalanche1 我有点同意,但是当我更喜欢避免“完美的理论”解决方案并做“实用的”事情时,就是这种情况。因此,与其花大量时间找出正确的类型和/或通过使用复杂的联合或类似方法使代码难以阅读,我们只需让它工作并继续前进。如果在任何时候有一个简单易用的解决方案,我们只需用新的更好的解决方案替换“不完美但有效”的解决方案。