TypeScript - використовуйте правильну версію setTimeout (вузол проти вікна)


115

Я працюю над оновленням старого коду 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: Тип "Таймер" не можна призначити типу "номер".


Чи є у вас тип: ["node"] у вашому tsconfig.json? Дивіться stackoverflow.com/questions/42940954/…
koe

@koe Ні, у мене немає опції type: ["node"] у файлі tsconfig. Але типи вузлів втягуються як залежність npm від чогось іншого.
Kevin Tighe

1
Ви також можете чітко визначити "типи" у tsconfig.json - коли ви опускаєте "вузол", він не використовується у компіляції. напр. "types": ["jQuery"]
koe

1
Дивно, що відповідь @koe (використовувати параметр "типи") не має жодних голосів, є єдиною вірною правильною відповіддю.
Єгор Непомнящих

@ KevinTighe typesне включає, nodeале setTimeoutвсе одно отримує тип Node, а не тип браузера. typesпо замовчуванням для всіх типів в node_modules/@types, як описано в typescriptlang.org/tsconfig#types , але навіть якщо ви робите вказати typesі НЕ включають в себе "node", чому до setTimeoutцих пір отримати його тип вузла , і як ви можете отримати тип браузера? Рішення @ Axke є трохи хакерським, в основному кажучи, що воно повертає те, що повертає. Можливо, TypeScript все ще знаходить неправильний тип, але принаймні він буде постійно помилковим.
Денис Хоу,

Відповіді:


88

Ще одне обхідне рішення, яке не впливає на оголошення змінних:

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

Крім того, повинна бути можливість використовувати windowоб'єкт явно без any:

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

25
Я думаю, що інший ( window.setTimeout) повинен бути правильною відповіддю на це питання, оскільки це найбільш чітке рішення.
amik

5
Якщо ви використовуєте anyтип, ви насправді не даєте відповіді TypeScript.
С ..

так само numberтип призведе до специфічних помилок шрифту TypeScript, оскільки setTimeoutфункція вимагає більше, ніж це.
С ..

1
window.setTimeoutможе спричинити проблеми з модульними тестовими фреймворками (node.js). Найкраще рішення - використовувати let n: NodeJS.Timeoutі n = setTimeout.
камерлен

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

clearTimeout(timer);

За допомогою ReturnType<fn>ви отримуєте незалежність від платформи. Ви не будете змушені використовувати anyні , window.setTimeoutякі будуть ламатися при запуску коду немає сервера nodeJS (наприклад, на стороні сервера візуалізації сторінки).


Хороші новини, це також сумісно з Deno!


9
Я розумію, що це правильна відповідь, і вона повинна бути прийнятою, оскільки вона забезпечує правильне визначення типу для кожної платформи, що підтримує setTimeout/ clearTimeoutі не використовує any.
afenster

11
Це рішення, якщо ви пишете бібліотеку, яка працює як на NodeJS, так і в браузері.
yqlim

Тип повернення - NodeJS.Timeoutякщо використовується setTimeoutбезпосередньо та numberякщо використовується window.setTimeout. Не потрібно використовувати ReturnType.
камерлен

@cchamberlain Вона вам потрібна під час запуску setTimeoutфункції та очікує, що її результат буде збережений у змінній. Спробуйте самі на дитячому майданчику TS.
Akxe

Щодо OP та мене, TypeScript отримує setTimeoutнеправильний тип (з причин, яких ніхто не може пояснити), але принаймні це рішення має замаскувати це дещо кращим чином, ніж просто використання any.
Денис Хоу,

15

Я думаю, це залежить від того, де ви будете запускати свій код.

Якщо вашою метою виконання є серверний Node JS, використовуйте:

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

Якщо вашою метою виконання є браузер, використовуйте:

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

5

Це, швидше за все, працюватиме зі старими версіями, але з версією 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);

1
Подібним чином, якщо вам потрібна версія браузера setTimeout, щось на зразок const { setTimeout } = windowвидалить ці помилки.
Джек Стім

4

Я зіткнувся з тією ж проблемою, і обхідний шлях, який вирішила використовувати наша команда, полягав лише у використанні "будь-якого" для типу таймера. Наприклад:

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

Він буде працювати з обома реалізаціями методів setTimeout / setInterval / clearTimeout / clearInterval.


2
Так, це працює. Я також зрозумів, що можу просто вказати метод безпосередньо на об'єкті window: window.setTimeout (...). Не впевнений, що це найкращий спосіб піти, але поки я дотримуватимусь цього.
Kevin Tighe

1
Ви можете правильно імпортувати простір імен NodeJS у машинопис, див. Цю відповідь .
hlovdal

Щоб насправді відповісти на запитання ("як я можу доручити компілятору вибрати потрібну мені версію"), ви можете використовувати замість цього window.setTimeout (), як @dhilt відповів нижче.
Ансон ВанДорен

window.setTimeoutможе не працювати з рамками модульних тестів. Існує тип, який можна використовувати тут .. Його NodeJS.Timeout. Можливо, ви думаєте, що не перебуваєте у середовищі вузлів, але я маю для вас новини: Webpack / TypeScript тощо виконують node.js.
камерлен

1
  • Якщо ви хочете отримати реальне рішення для машинопису про таймери, ми переходимо до:

    Помилка повертається типу 'номер', це не Таймер або щось інше.

    Це для рішення версій машинописів ~> 2.7:

export type Tick = null | number | NodeJS.Timer;

Тепер ми все виправили, просто оголосимо так:

 import { Tick } from "../../globals/types";

 export enum TIMER {
    INTERVAL = "INTERVAL",
    TIMEOUT = "TIMEOUT", 
 };

 interface TimerStateI {
   timeInterval: number;
 }

 interface TimerI: TimerStateI {
   type: string;
   autoStart: boolean;
   isStarted () : bool;
 }

     class myTimer extends React.Component<TimerI, TimerStateI > {

          private myTimer: Tick = null;
          private myType: string = TIMER.INTERVAL;
          private started: boll = false;

          constructor(args){
             super(args);
             this.setState({timeInterval: args.timeInterval});

             if (args.autoStart === true){
               this.startTimer();
             }
          }

          private myTick = () => {
            console.log("Tick");
          }    

          private startTimer = () => {

            if (this.myType === TIMER.INTERVAL) {
              this.myTimer = setInterval(this.myTick, this.timeInterval);
              this.started = true;
            } else if (this.myType === TIMER.TIMEOUT) {
              this.myTimer = setTimeout(this.myTick, this.timeInterval);
              this.started = true;
            }

          }

         private isStarted () : bool {
           return this.started;
         }

     ...
     }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.