Saya sedang mengerjakan kelas yang dilewati pengguna dalam metode yang menggunakan argumen yang ditentukan pengguna, atau tanpa argumen. Kelas memperlihatkan metode "panggilan" yang harus dipanggil dengan argumen yang diminta dalam metode pengguna, melakukan berbagai operasi, lalu memanggil metode pengguna dan mengembalikan hasilnya.

Ini berfungsi dengan baik jika fungsi pengguna memiliki argumen, tetapi rusak jika fungsi pengguna tidak menggunakan argumen.

Inilah kode saya saat ini (saya terus mencoba variasi yang berbeda tetapi belum menemukan yang benar):

class Test<T, TResult> {
  methodToRun: (data?: T) => TResult;

  constructor(methodToRun: (data?: T)=>TResult){
    this.methodToRun = methodToRun;
  }

  call(data: T) {
    // do some stuff, then...
    return data === undefined ? this.methodToRun(data) : this.methodToRun();
  }
}

const test = new Test(function(data: string){
  return 1;
});

test.call("");
test.call(); // should be a signature error (currently is)

const test2 = new Test(function(data: number){
  return 1;
});

test2.call(1);

const test3 = new Test(function(data: {foo: string, bar: number}){
  return 1;
});

test3.call({foo: "", bar: 1});


const test4 = new Test(function(){
 return 1;
});

test4.call(); // should be allowed (currently errors "Supplied Paramaters do not match any signature of call target")
test4.call({}); // should not be allowed (currently is)

Salah satu variasi yang saya coba adalah menyesuaikan tanda tangan panggilan:

call()
call(data: T) 
call(data?: T) {

Namun itu menyebabkan test.call() dan test4.call() diizinkan. Bagaimana cara saya mendapatkan TypeScript untuk memastikan bahwa tanda tangan panggilan selalu cocok dengan tanda tangan fungsi pengguna?

0
Ashley Reid 17 Maret 2017, 16:23

2 jawaban

Jawaban Terbaik

Berikut cara melakukannya dengan new ():

interface OneOrNoArgFunc<T, TResult> {
    (arg?: T): TResult;
}

class Test<T, TResult, TF extends OneOrNoArgFunc<T, TResult>> {
    methodToRun: TF;

    constructor(methodToRun: TF) {
        this.methodToRun = methodToRun;
    }

    call = ((arg?: T): TResult => {
        return this.methodToRun(arg);
    }) as TF;
}

let a = new Test((foo: string) => foo + "hello");
let b = new Test(() => 33);
let c = new Test((foo: string, bar: number) => foo + bar); // ERR, cannot have more than one argument

a.call("argument"); // OK
a.call(33); // ERR, wrong type
a.call(); // ERR, need argument

b.call();
b.call("argument"); // ERR, no arguments

Ini memang membutuhkan pemeran as TF eksplisit, dan generik tidak mengetik dengan sangat membantu: TypeScript menandai a sebagai a: Test<{}, {}, (foo: string) => string>, tampaknya generik yang disimpulkan tidak melewati fungsi.


Sunting

Anda dapat menyimpan parameter dan mengembalikan parameter generik tipe dengan menggunakan tipe persimpangan:

constructor(methodToRun: ((arg?: T) => TResult) & TF) {
    this.methodToRun = methodToRun;
}

/* ... */

// a: Test<string, string, (foo: string) => string>
let a = new Test((foo: string) => foo + "hello");

// b: Test<{}, number, () => number>
let b = new Test(() => 33);
1
y2bd 17 Maret 2017, 17:53

Ini agak bertele-tele tetapi berfungsi dengan persyaratan Anda:

interface TestWithParams<T, TResult> {
    call(data: T): TResult;
}

interface TestWithoutParams<T, TResult> {
    call(): TResult;
}

interface TestConstructor {
    new<T, TResult>(methodToRun: () => TResult): TestWithoutParams<T, TResult>;
    new<T, TResult>(methodToRun: (data: T) => TResult): TestWithParams<T, TResult>;
}

class Test<T, TResult> {
    methodToRun: (data?: T) => TResult;

    constructor(methodToRun: (data?: T) => TResult) {
        this.methodToRun = methodToRun;
    }

    call(data?: T) {
        // do some stuff, then...
        return data === undefined ? this.methodToRun(data) : this.methodToRun();
    }
}

const test = new (Test as TestConstructor)(function (data: string) {
    return 1;
});

test.call("");
test.call(); // error

const test2 = new (Test as TestConstructor)(function (data: number) {
    return 1;
});

test2.call(1);

const test3 = new (Test as TestConstructor)(function (data: { foo: string, bar: number }) {
    return 1;
});

test3.call({foo: "", bar: 1});

const test4 = new (Test as TestConstructor)(function () {
    return 1;
});

test4.call(); // ok
test4.call({}); // error

(kode di taman bermain)


Sunting

Satu-satunya cara yang dapat saya pikirkan untuk melakukannya tanpa (Test as TestConstructor) adalah dengan menggunakan "metode pabrik":

class Test<T, TResult> {
    methodToRun: (data?: T) => TResult;

    static create<T, TResult>(methodToRun: () => TResult): TestWithoutParams<T, TResult>;
    static create<T, TResult>(methodToRun: (data: T) => TResult): TestWithParams<T, TResult>;
    static create<T, TResult>(methodToRun: (data?: T) => TResult): Test<T, TResult> {
        return new Test(methodToRun);
    }

    private constructor(methodToRun: (data?: T) => TResult) {
        this.methodToRun = methodToRun;
    }

    call(data?: T) {
        // do some stuff, then...
        return data === undefined ? this.methodToRun(data) : this.methodToRun();
    }
}

Lalu:

const test = Test.create(function (data: string) {
    return 1;
});

...

(kode di taman bermain< /a>)

Tampaknya sangat elegan, tetapi ini memperkenalkan fungsi tambahan ke Test yang juga akan ada di js yang dikompilasi.
Cara sebelumnya adalah TypeScript yang ketat dan tidak menghasilkan js yang lebih "bengkak".

0
Nitzan Tomer 17 Maret 2017, 15:12