TypeScript中setTimeout的典型错误:window vs Node.js

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

“错误的setTimeout”:TypeScript如何让开发者抓狂

\n

您是否曾经在TypeScript中编写过一个简单的定时器,然后代码编辑器突然开始把所有内容都标红?熟悉的场景:您导入setTimeout,编写const timer = setTimeout(...),然后TypeScript报错或提示一个难以理解的类型。问题出在哪里?原来setTimeout有两个版本——一个用于浏览器(window),另一个用于服务器(Node.js)。而追求完美的TypeScript迫使我们做出选择。

\n

这个话题是Stack Overflow上最热门的话题之一。成千上万的开发者在同一个坑里跌倒。但我们不会只是简单地复制答案。我们将深入问题的根源,理解类型化的工作原理,并学会编写在浏览器和Node.js中都不会出错的代码。

\n\n

为什么会有两个版本的setTimeout?

\n

从历史上看,JavaScript存在于两个世界中:客户端(浏览器)和服务器端(Node.js)。在浏览器中,setTimeout是全局对象window的一个方法。在Node.js中,它是来自全局模块timers的一个函数。它们做同样的事情,但它们的类型签名不同。

\n

以下是关键区别:

\n
    \n
  • 返回值:在浏览器中,window.setTimeout返回一个number(定时器标识符)。在Node.js中,它返回一个NodeJS.Timeout对象。
  • \n
  • 上下文:在浏览器中,回调函数内部的this是window(在非严格模式下)。在Node.js中,它是定时器对象。
  • \n
  • 类型化:TypeScript为不同的环境提供了不同的声明。如果您编写通用代码(同构应用),就会发生冲突。
  • \n
\n\n

问题在代码中如何体现

\n

假设您正在开发一个必须在客户端和服务器端都能运行的库。您编写了这样的代码:

\n
// utils.ts\nexport function delay(ms: number): Promise<void> {\n    return new Promise(resolve => {\n        const timer = setTimeout(resolve, ms);\n        // timer的类型是什么?number还是NodeJS.Timeout?\n    });\n}
\n

TypeScript会发现setTimeout可能有两种类型,并报错:“类型 'Timeout' 不能赋值给类型 'number'。” 或者反过来,具体取决于tsconfig.json的设置和已包含的类型。

\n

为了更直观,下面是通过ReturnType查看类型时的样子:

\n
// 在浏览器中 (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// 在Node.js中 (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout
\n\n

针对不同场景的正确解决方案

\n

让我们看看四种方法。选择适合您项目的那一种。

\n\n

1. 如果您只针对浏览器编写代码

\n

最简单的情况。只需将类型指定为number并显式使用window.setTimeout:

\n
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n    console.log('一秒过去了');\n}, 1000);\n\n// 清除定时器\nwindow.clearTimeout(timerId);
\n

为什么这样有效?在浏览器环境中,window.setTimeout总是返回一个数字。如果您包含了lib: ["dom"],TypeScript知道这一点。

\n\n

2. 如果您只针对Node.js编写代码

\n

这里的类型是NodeJS.Timeout。直接使用它:

\n
// node-timer.ts\nimport { setTimeout } from 'timers'; // 显式导入以保持清晰\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n    console.log('定时器触发了');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout接受NodeJS.Timeout
\n

重要提示:在Node.js中,clearTimeout也接受No

博客

书籍推荐