Các hàm được gõ mạnh như các tham số có thể có trong TypeScript?


559

Trong TypeScript, tôi có thể khai báo một tham số của hàm là Hàm loại. Có cách nào "an toàn kiểu" để làm điều này mà tôi đang thiếu không? Ví dụ, hãy xem xét điều này:

class Foo {
    save(callback: Function) : void {
        //Do the save
        var result : number = 42; //We get a number from the save operation
        //Can I at compile-time ensure the callback accepts a single parameter of type number somehow?
        callback(result);
    }
}

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

Lưu lại cuộc gọi không phải là loại an toàn, tôi cung cấp cho nó một hàm gọi lại trong đó tham số của hàm là một chuỗi nhưng tôi đang truyền cho nó một số và biên dịch không có lỗi. Tôi có thể tạo tham số kết quả trong việc lưu chức năng loại an toàn không?

Phiên bản TL; DR: có tương đương với đại biểu .NET trong TypeScript không?

Câu trả lời:


805

Chắc chắn rồi. Kiểu của hàm bao gồm các kiểu đối số và kiểu trả về của nó. Ở đây chúng tôi xác định rằng callbackloại của tham số phải là "hàm chấp nhận một số và trả về loại any":

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

var strCallback = (result: string) : void => {
    alert(result);
}
var numCallback = (result: number) : void => {
    alert(result.toString());
}

foo.save(strCallback); // not OK
foo.save(numCallback); // OK

Nếu bạn muốn, bạn có thể xác định một bí danh loại để gói gọn điều này:

type NumberCallback = (n: number) => any;

class Foo {
    // Equivalent
    save(callback: NumberCallback) : void {
        callback(42);
    }
}

6
(n: number) => anycó nghĩa là bất kỳ chữ ký chức năng?
nikk wong

16
@nikkwong có nghĩa là hàm có một tham số (a number) nhưng kiểu trả về hoàn toàn không bị hạn chế (có thể là bất kỳ giá trị nào hoặc thậm chí void)
Daniel Earwicker

16
Điểm ntrong cú pháp này là gì? Các loại đầu vào và đầu ra không đủ?
Yuhuan Jiang

4
Một tác dụng phụ giữa việc sử dụng các hàm nội tuyến so với các hàm được đặt tên (câu trả lời bên dưới so với câu trả lời này) là biến "này" không được xác định với hàm được đặt tên trong khi nó được xác định trong hàm nội tuyến. Không có gì ngạc nhiên đối với các lập trình viên JavaScript nhưng chắc chắn không rõ ràng đối với các nền tảng mã hóa khác.
Stevko

3
@YuhuanJiang Bài đăng này có thể được bạn quan tâm
Ophidian

93

Dưới đây là tương đương TypeScript của một số đại biểu .NET phổ biến:

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

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

2
Có lẽ hữu ích để xem xét nhưng nó sẽ là một mô hình chống thực sự sử dụng các loại như vậy. Dù sao, những người đó trông giống các loại Java SAM hơn là các đại biểu C #. Tất nhiên chúng không phải và chúng tương đương với dạng bí danh chỉ thanh lịch hơn cho các chức năng
Aluan Haddad

5
@AluanHaddad bạn có thể giải thích lý do tại sao bạn nghĩ đây là một mô hình chống?
Max R McCarty

8
Lý do là TypeScript có một cú pháp nghĩa đen kiểu hàm ngắn gọn mà không cần đến các giao diện như vậy. Trong các đại biểu C # là danh nghĩa, nhưng cả đại biểu ActionFuncđại biểu đều đáp ứng hầu hết nhu cầu về các loại đại biểu cụ thể và, thật thú vị, cung cấp cho C # một ngữ nghĩa của kiểu gõ cấu trúc. Nhược điểm của các đại biểu này là tên của họ không có ý nghĩa gì nhưng những ưu điểm khác thường vượt trội hơn điều này. Trong TypeScript đơn giản là chúng ta không cần những loại này. Vì vậy, các mô hình chống sẽ là function map<T, U>(xs: T[], f: Func<T, U>). Ưu tiênfunction map<T, U>(xs: T[], f: (x: T) => U)
Aluan Haddad

6
Đó là vấn đề của hương vị, vì đây là những hình thức tương đương trong một ngôn ngữ không có loại thời gian chạy. Ngày nay bạn cũng có thể sử dụng các bí danh thay vì giao diện.
Drew Noakes

18

Tôi nhận ra bài đăng này đã cũ, nhưng có một cách tiếp cận nhỏ gọn hơn một chút so với những gì được hỏi, nhưng có thể là một sự thay thế rất hữu ích. Bạn về cơ bản có thể khai báo hàm in-line khi gọi phương thức ( Foo's save()trong trường hợp này). Nó sẽ trông giống như thế này:

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("Resulting boolean: " + result)
    }
}

var foo = new Foo()

// Single callback example.
// Just like with @RyanCavanaugh's approach, ensure the parameter(s) and return
// types match the declared types above in the `save()` method definition.
foo.save((newNumber: number) => {
    console.log("Some number: " + newNumber)

    // This is optional, since "any" is the declared return type.
    return newNumber
})

// Multiple callbacks example.
// Each call is on a separate line for clarity.
// Note that `firstCallback()` has a void return type, while the second is boolean.
foo.multipleCallbacks(
    (s: string) => {
         console.log("Some string: " + s)
    },
    (b: boolean) => {
        console.log("Some boolean: " + b)
        let result = b && false

        return result
    }
)

Cách multipleCallback()tiếp cận này rất hữu ích cho những thứ như các cuộc gọi mạng có thể thành công hoặc thất bại. Một lần nữa giả sử một ví dụ về cuộc gọi mạng, khi multipleCallbacks()được gọi, hành vi cho cả thành công và thất bại có thể được xác định tại một điểm, điều này tạo ra sự rõ ràng hơn cho các trình đọc mã trong tương lai.

Nói chung, theo kinh nghiệm của tôi, cách tiếp cận này cho vay ngắn gọn hơn, ít lộn xộn hơn và tổng thể rõ ràng hơn.

Chúc tất cả may mắn!


16
type FunctionName = (n: inputType) => any;

class ClassName {
    save(callback: FunctionName) : void {
        callback(data);
    }
}

Điều này chắc chắn phù hợp với mô hình lập trình chức năng.


6
Bạn nên gọi nó inputTypechứ không phải returnType, không nên? Đâu inputTypelà loại datamà bạn truyền tham số cho callbackhàm.
ChrisW

Có @ChrisW bạn đúng, inputType có ý nghĩa hơn. Cảm ơn!
Krishna Ganeriwal

2

Trong TS, chúng ta có thể nhập các hàm theo cách sau:

Các loại chức năng / chữ ký

Điều này được sử dụng để triển khai thực sự các hàm / phương thức, nó có cú pháp sau:

(arg1: Arg1type, arg2: Arg2type) : ReturnType

Thí dụ:

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

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

}

Loại chức năng

Kiểu chữ là một cách khác để khai báo kiểu của hàm. Chúng thường được áp dụng trong chữ ký hàm của hàm bậc cao hơn. Hàm bậc cao hơn là hàm chấp nhận các hàm làm tham số hoặc trả về hàm. Nó có cú pháp sau:

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

Thí dụ:

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

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

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

}

1

Nếu bạn xác định loại hàm trước thì nó sẽ giống như

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); //--will be showing error
foo.save(numberCallback);

Không có loại hàm bằng cách sử dụng cú pháp thuộc tính đơn giản, nó sẽ là:

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); //--will be showing error
foo.save(numberCallback);

Nếu bạn muốn bằng cách sử dụng một chức năng giao diện như đại biểu c # chung thì đó sẽ là:

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); //--will be showing error
foo.save(numberCBObj);

0

Bên cạnh những gì đã nói, một vấn đề phổ biến là khai báo các loại hàm tương tự bị quá tải. Trường hợp điển hình là phương thức EventEuctor trên () sẽ chấp nhận nhiều loại người nghe. Điều tương tự có thể xảy ra Khi làm việc với các hành động redux - và ở đó bạn sử dụng loại hành động là nghĩa đen để đánh dấu sự quá tải, Trong trường hợp EventEmitters, bạn sử dụng loại sự kiện theo nghĩa đen:

interface MyEmitter extends EventEmitter {
  on(name:'click', l: ClickListener):void
  on(name:'move', l: MoveListener):void
  on(name:'die', l: DieListener):void
  //and a generic one
  on(name:string, l:(...a:any[])=>any):void
}

type ClickListener = (e:ClickEvent)=>void
type MoveListener = (e:MoveEvent)=>void
... etc

// will type check the correct listener when writing something like:
myEmitter.on('click', e=>...<--- autocompletion
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.