8

Возможно ли использование функций с строгой типизацией в качестве параметров в TypeScript?

1

В TypeScript я могу объявить параметр функции как тип Function. Существует ли "безопасный с точки зрения типов" способ сделать это, который я упустил? Например, рассмотрим следующий код:

class Foo {
    save(callback: Function): void {
        // Выполняем сохранение
        var result: number = 42; // Мы получаем число из операции сохранения
        // Могу ли я на этапе компиляции гарантировать, что коллбэк принимает единственный параметр типа number?
        callback(result);
    }
}

var foo = new Foo();
var callback = (result: string): void => {
    alert(result);
}
foo.save(callback);

Коллбэк save не является безопасным по типам: я передаю функцию обратного вызова, у которой параметр - строка, но передаю ей число, и это компилируется без ошибок. Могу ли я сделать параметр result в save безопасным по типам?

Краткая суть вопроса: существует ли эквивалент делегата .NET в TypeScript?

5 ответ(ов)

1

Вот эквиваленты некоторых распространенных делегатов .NET на TypeScript:

interface Action<T> {
    (item: T): void;
}

interface Func<T, TResult> {
    (item: T): TResult;
}

Action<T> представляет собой делегат, который принимает один параметр типа T и не возвращает значения, тогда как Func<T, TResult> принимает параметр типа T и возвращает значение типа TResult. Эти интерфейсы могут быть полезны для определения типов колбэков и других функций в вашем TypeScript коде.

0

Ваша реализация действительно соответствует парадигме функционального программирования. Вы определили тип FunctionName, который представляет собой функцию, принимающую аргумент n типа inputType и возвращающую значение любого типа (any). Затем в классе ClassName метод save принимает в качестве параметра callback функции этого типа, что позволяет передать любую функцию, соответствующую этому интерфейсу. Внутри метода save вы вызываете этот callback с некоторыми данными (data), что демонстрирует функциональный подход к обработке данных.

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

0

Я понимаю, что этот пост старый, но есть более компактный подход, который немного отличается от заданного вопроса, но может быть очень полезной альтернативой. Вы можете фактически объявить функцию на месте при вызове метода (save() в данном случае класса Foo). Это будет выглядеть примерно так:

class Foo {
    save(callback: (n: number) => any): void {
        callback(42);
    }

    multipleCallbacks(firstCallback: (s: string) => void, secondCallback: (b: boolean) => boolean): void {
        firstCallback("hello world");

        let result: boolean = secondCallback(true);
        console.log("Результирующее значение: " + result);
    }
}

var foo = new Foo();

// Пример с одиночным обратным вызовом.
// Как и в подходе @RyanCavanaugh, убедитесь, что параметры и типы возвращаемых
// значений соответствуют объявленным типам выше в определении метода `save()`.
foo.save((newNumber: number) => {
    console.log("Некоторое число: " + newNumber);

    // Это необязательно, так как "any" - объявленный тип возвращаемого значения.
    return newNumber;
});

// Пример с несколькими обратными вызовами.
// Каждый вызов вынесен на отдельную строку для ясности.
// Обратите внимание, что `firstCallback()` имеет тип возвращаемого значения void, а второй - boolean.
foo.multipleCallbacks(
    (s: string) => {
         console.log("Некоторая строка: " + s);
    },
    (b: boolean) => {
        console.log("Некоторое булево значение: " + b);
        let result = b && false;

        return result;
    }
);

Подход multipleCallbacks() очень полезен для таких вещей, как сетевые вызовы, которые могут завершиться успешно или неудачно. Предполагая пример сетевого вызова, при вызове multipleCallbacks() можно определить поведение как для успеха, так и для неудачи в одном месте, что делает код более понятным для будущих читателей.

В общем, исходя из моего опыта, этот подход позволяет сделать код более лаконичным, менее загроможденным и более ясным в целом.

Удачи всем!

0

В TypeScript мы можем описывать функции несколькими способами:

Типы/подписи функций

Этот способ используется для реальных реализаций функций/методов и имеет следующую синтаксическую структуру:

(arg1: Arg1type, arg2: Arg2type) : ReturnType

Пример:

function add(x: number, y: number): number {
    return x + y;
}

class Date {
  setTime(time: number): number {
   // ...
  }
}

Литеральные типы функций

Литеральные типы функций - это еще один способ определить тип функции. Обычно они применяются в сигнатурах функций высшего порядка. Функция высшего порядка - это функция, которая принимает функции в качестве параметров или возвращает функцию. Синтаксис следующий:

(arg1: Arg1type, arg2: Arg2type) => ReturnType

Пример:

type FunctionType1 = (x: string, y: number) => number;

class Foo {
    save(callback: (str: string) => void) {
       // ...
    }

    doStuff(callback: FunctionType1) {
       // ...
    }
}

Эти подходы позволяют четко определять типы аргументов и возвращаемых значений, что способствует более безопасному и предсказуемому коду в TypeScript.

0

Если вы сначала определите тип функции, он будет выглядеть следующим образом:

type Callback = (n: number) => void;

class Foo {
    save(callback: Callback) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

foo.save(stringCallback); //--будет ошибка
foo.save(numberCallback);

Без типа функции, используя обычный синтаксис свойств, это будет:

class Foo {
    save(callback: (n: number) => void) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

foo.save(stringCallback); //--будет ошибка
foo.save(numberCallback);

Если вы хотите использовать интерфейс функции, как в делегатах Generics C#, это будет выглядеть так:

interface CallBackFunc<T, U>
{
    (input:T): U;
};

class Foo {
    save(callback: CallBackFunc<number,void>) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

let strCBObj: CallBackFunc<string, void> = stringCallback;
let numberCBObj: CallBackFunc<number, void> = numberCallback;

foo.save(strCBObj); //--будет ошибка
foo.save(numberCBObj);

В данном примере видно, что попытка передать stringCallback в метод save всегда будет вызывать ошибку, так как он принимает только функции, которые соответствуют ожидаемому типу, в данном случае (n: number) => void.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь