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.