Определение типа обратного вызова TypeScript

173

У меня есть следующий класс в TypeScript:

class CallbackTest
{
    public myCallback;

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}

Я использую класс, как это:

var test = new CallbackTest();
test.myCallback = () => alert("done");
test.doWork();

Код работает, поэтому он отображает окно сообщения, как и ожидалось.

Мой вопрос: есть ли какой-нибудь тип, который я могу предоставить для своего поля класса myCallback? Прямо сейчас, публичное поле myCallbackимеет тип, anyкак показано выше. Как я могу определить сигнатуру метода обратного вызова? Или я могу просто установить тип для какого-то типа обратного вызова? Или я могу сделать ничего из этого? Должен ли я использовать any(неявный / явный)?

Я пробовал что-то вроде этого, но это не сработало (ошибка времени компиляции):

public myCallback: ();
// or:
public myCallback: function;

Я не смог найти объяснения этому в Интернете, поэтому надеюсь, что вы мне поможете.

nikeee
источник

Ответы:

212

Я только что нашел что-то в спецификации языка TypeScript, это довольно просто. Я был довольно близко

синтаксис следующий:

public myCallback: (name: type) => returntype;

В моем примере это было бы

class CallbackTest
{
    public myCallback: () => void;

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}
nikeee
источник
8
Я не понимаю, почему имя параметра требуется для определения сигнатуры обратного вызова ...
2grit
4
Я думаю, это может быть культурное наследие от команды C # , думаю, мне все-таки нравится ...
2grit
@nikeee Можете ли вы предоставить ссылку на эту страницу документации?
Jcairney
Это может быть хорошей ссылкой fettblog.eu/typescript-substitutability
Нилакантха Singh Део
148

Чтобы пойти еще дальше, вы можете объявить указатель типа на сигнатуру функции, например:

interface myCallbackType { (myArgument: string): void }

и используйте это так:

public myCallback : myCallbackType;
Ленг
источник
9
Это (IMO) гораздо лучшее решение, чем принятый ответ, потому что оно позволяет вам определить тип, а затем, скажем, передать параметр этого типа (обратный вызов), который затем вы можете использовать любым способом, включая его вызов. В принятом ответе используется переменная-член, и вам необходимо установить переменную-член для своей функции, а затем вызвать метод - безобразно и подвержен ошибкам, поскольку сначала установка переменной является частью договора о вызове метода.
Дэвид
Это также позволяет вам легко установить обратный вызов как обнуляемый, напримерlet callback: myCallbackType|null = null;
Doches
1
Обратите внимание, что TSLint будет жаловаться "TSLint: Интерфейс имеет только подпись вызова - используйте type MyHandler = (myArgument: string) => voidвместо этого. (Callable-types)" ; см . ответ TSV
Арджан
Предыдущий проект этого ответа фактически решил проблему, которая привела меня к этому вопросу. Я пытался определить достаточно разрешающую сигнатуру функции в интерфейсе, который мог бы принимать любое количество параметров, не вызывая ошибки компилятора. Ответ в моем случае должен был использовать ...args: any[]. Пример: интерфейс экспорта MyInterface {/ ** Функция обратного вызова. / callback: (... args: any []) => any, / * Параметры для функции обратного вызова. * / callbackParams: любой []}
Кен Лион
62

Вы можете объявить новый тип:

declare type MyHandler = (myArgument: string) => void;

var handler: MyHandler;

Обновить.

declareКлючевое слово не нужно. Его следует использовать в файлах .d.ts или в подобных случаях.

TSV
источник
Где я могу найти документацию для этого?
Е. Сундин
@ E.Sundin - Раздел "Псевдонимы типов" of typescriptlang.org/docs/handbook/advanced-types.html
TSV
1
Хотя это правда и приятно знать, на той же странице (в настоящее время) также говорится: «Поскольку идеальное свойство программного обеспечения открыто для расширения, вы всегда должны использовать интерфейс поверх псевдонима типа, если это возможно».
Арджан
@Arjan - Я полностью согласен с этим для объектов. Не могли бы вы уточнить - как вы хотите расширить функцию?
TSV
Обратите внимание, что объявление типа является необязательным: var handler: (myArgument: string) => voidсинтаксически допустимо (если немного грязно).
Хатч
36

Вот пример - не принимать параметры и ничего не возвращать.

class CallbackTest
{
    public myCallback: {(): void;};

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}

var test = new CallbackTest();
test.myCallback = () => alert("done");
test.doWork();

Если вы хотите принять параметр, вы также можете добавить его:

public myCallback: {(msg: string): void;};

И если вы хотите вернуть значение, вы можете добавить это также:

public myCallback: {(msg: string): number;};
Фентон
источник
Функционально они идентичны - они определяют одно и то же и дают вам проверку типа сигнатуры функции. Вы можете использовать то, что вы предпочитаете. Спецификация говорит, что они есть exactly equivalent.
Фентон
6
@nikeee: Вопрос скорее в том, что отличается от твоего ответа? Стив разместил свой ответ перед вашим.
jgauffin
@jgauffin Действительно, результат тот же. IMO решение, которое я разместил, более естественно, когда речь идет об обратных вызовах, поскольку версия Стива допускает целые определения интерфейса. Это зависит от ваших предпочтений.
nikeee
@Fenton, не могли бы вы предоставить ссылку на эту документацию?
Jcairney
18

Если вы хотите универсальную функцию, вы можете использовать следующее. Хотя, кажется, это нигде не задокументировано.

class CallbackTest {
  myCallback: Function;
}   
Пепельно-синий
источник
4

Вы можете использовать следующее:

  1. Тип Alias ​​(используя typeключевое слово, псевдоним функции литерал)
  2. Интерфейс
  3. Функция Literal

Вот пример того, как их использовать:

type myCallbackType = (arg1: string, arg2: boolean) => number;

interface myCallbackInterface { (arg1: string, arg2: boolean): number };

class CallbackTest
{
    // ...

    public myCallback2: myCallbackType;
    public myCallback3: myCallbackInterface;
    public myCallback1: (arg1: string, arg2: boolean) => number;

    // ...

}
Виллем ван дер Веен
источник
2

Я столкнулся с той же ошибкой при попытке добавить обратный вызов для прослушивателя событий. Как ни странно, установка типа обратного вызова в EventListener решила это. Это выглядит более элегантно, чем определение всей сигнатуры функции как типа, но я не уверен, что это правильный способ сделать это.

class driving {
    // the answer from this post - this works
    // private callback: () => void; 

    // this also works!
    private callback:EventListener;

    constructor(){
        this.callback = () => this.startJump();
        window.addEventListener("keydown", this.callback);
    }

    startJump():void {
        console.log("jump!");
        window.removeEventListener("keydown", this.callback);
    }
}
Kokodoko
источник
нравится это. Но где другой класс в действии?
Яро
2

Я немного опоздал, но, так как некоторое время назад в TypeScript вы могли определить тип обратного вызова с

type MyCallback = (KeyboardEvent) => void;

Пример использования:

this.addEvent(document, "keydown", (e) => {
    if (e.keyCode === 1) {
      e.preventDefault();
    }
});

addEvent(element, eventName, callback: MyCallback) {
    element.addEventListener(eventName, callback, false);
}
Даниил
источник