我正在处理一个类,用户在一个采用用户定义的参数或没有参数的方法中传递给它。该类公开了一个“调用”方法,该方法必须使用用户方法中请求的参数调用,执行各种操作,然后调用用户的方法并返回结果。
如果用户的函数有参数,则可以正常工作,但如果用户的函数不使用参数,则会中断。
这是我当前的代码(我一直尝试不同的变体,但尚未找到正确的代码):
call()
call(data: T)
call(data?: T) {
我尝试的其中一个变体是调整呼叫的签名:
struct B1 {
B1(int);
};
struct D1 : B1 {
using B1::B1;
// The set of candidate inherited constructors is
// 1. B1(const B1&)
// 2. B1(B1&&)
// 3. B1(int)
// D1 has the following constructors:
// 1. D1()
// 2. D1(const D1&)
// 3. D1(D1&&)
// 4. D1(int) <- inherited
};
struct B2 {
B2(int = 13, int = 42);
};
struct D2 : B2 {
using B2::B2;
// The set of candidate inherited constructors is
// 1. B2(const B2&)
// 2. B2(B2&&)
// 3. B2(int = 13, int = 42)
// 4. B2(int = 13)
// 5. B2()
// D2 has the following constructors:
// 1. D2()
// 2. D2(const D2&)
// 3. D2(D2&&)
// 4. D2(int, int) <- inherited
// 5. D2(int) <- inherited
};
然而,这导致了test.call()和test4.call()都被允许。 如何获取打字稿以确保调用签名始终与用户函数的签名匹配?
答案 0 :(得分:1)
以下是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
这确实需要明确as TF
强制转换,而且泛型不是非常有用的:打字稿标记a
为a: Test<{}, {}, (foo: string) => string>
,似乎推断的泛型不是&#39; t通过了这个函数。
您可以使用交集类型保留参数和返回类型通用参数:
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 :(得分:0)
这有点冗长,但它适用于您的要求:
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
在没有(Test as TestConstructor)
的情况下,我能想到的唯一方法是使用&#34;工厂方法&#34;:
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();
}
}
然后:
const test = Test.create(function (data: string) {
return 1;
});
...
这似乎非常优雅,但这为Test
引入了一个额外的功能,它也将存在于已编译的js中。
以前的方式是严格的打字稿,并没有导致更多&#34;膨胀&#34; JS。