试图在通用方法上强制使用一个可选的默认参数,该参数必须是所传递对象的属性。这是一个示例:
function doIt<T>(target: T, propA: keyof T, propB: keyof T): string {
return `${target[propA]} ${target[propB]}`;
}
但是,在上面的示例中,我想将propA
和propB
默认设置为foo
和bar
,例如:
function doIt<T>(target: T, propA: keyof T = 'foo', propB: keyof T = 'bar'): string {
return `${target[propA]} ${target[propB]}`;
}
但是,当我这样做时,它抱怨Type '"bar"' is not assignable to type 'keyof T'.ts(2322)
是有意义的。
即使我将默认类型添加到T
,它也仍然不起作用:
function doIt<T = { foo: string; bar: string }>(target: T, propA: keyof T = 'foo', propB: keyof T = 'bar'): string {
return `${target[propA]} ${target[propB]}`;
}
基本上,我希望能够做到:
doIt({foo: 'jane', bar: 'doe'}); // should return 'jane doe'
doIt({zoo: 'jane', baz: 'doe'}); // should fail compilation
doIt({zoo: 'jane', baz: 'doe'}, 'zoo', 'baz'}); // should return 'jane doe'
有什么办法吗?
答案 0 :(得分:1)
如果不运行类型检查器,就无法轻松使用默认参数值。
获得所需内容的最简单方法可能是使用overloads描述调用doIt()
的不同预期方式,如下所示:
namespace Overloads {
function doIt<T>(target: T, propA: keyof T, propB: keyof T): string;
function doIt<T extends { bar: any }>(target: T, propA: keyof T): string;
function doIt<T extends { foo: any; bar: any }>(target: T): string;
function doIt(target: any, propA?: keyof any, propB?: keyof any): string {
if (typeof propA === "undefined") propA = "foo";
if (typeof propB === "undefined") propB = "bar";
return `${target[propA]} ${target[propB]}`;
}
console.log(doIt({ foo: "jane", bar: "doe" })); // jane doe
console.log(doIt({ zoo: "jane", baz: "doe" })); // error!
// ┌─────────────> ~~~~~~~~~~~~
// "zoo" not expected (although I'm surprised it doesn't complain about missing foo/bar)
console.log(doIt({ zoo: "jane", baz: "doe" }, "zoo", "baz")); // jane doe
console.log(doIt({ zoo: "jane", bar: "doe" }, "zoo")); // jane doe
}
这就是您想要的方式。请注意,这三个呼叫签名对应于不同数量的参数,并且它们在通用类型参数T
上分别具有不同的constraints。 (您可能希望使用string
而不是any
作为属性类型,...不确定)
请注意,在实现签名中,我将target
称为any
,propA
和propB
称为(keyof any) | undefined
。因此,此处的实现并不是特别安全的类型。过载并不能真正帮助实现类型的安全。它们更多地是为了呼叫者的利益。如果尝试使实现通用,则会遇到类似的问题,编译器无法验证"foo"
是否可分配给keyof T
,等等。
还要注意,我们不能在重载签名中使用默认参数值,但这很好...我们可以在函数实现中自己进行分配。如果参数为undefined
,请重新分配它们。
另一种方法是使用TypeScript的支持来处理rest parameters as tuple types,包括optional tuple elements:
namespace ConditionalTupleParameters {
function doIt<T>(
target: T,
...[propA, propB]: T extends { foo: any; bar: any }
? [(keyof T)?, (keyof T)?] // propA and propB are optional
: T extends { bar: any }
? [keyof T, (keyof T)?] // propA is required, propB is optional
: [keyof T, keyof T] // propA and propB are optional
): string {
if (typeof propA === "undefined") propA = "foo";
if (typeof propB === "undefined") propB = "bar";
return `${target[propA]} ${target[propB]}`;
}
console.log(doIt({ foo: "jane", bar: "doe" })); // jane doe
console.log(doIt({ zoo: "jane", baz: "doe" })); // error!
// ┌──────> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// expected 3 arguments, got 1
console.log(doIt({ zoo: "jane", baz: "doe" }, "zoo", "baz")); // jane doe
console.log(doIt({ zoo: "jane", bar: "doe" }, "zoo")); // jane doe
}
这与重载类似。看起来很丑陋,不是很好。但是,该实现实际上是相对类型安全的,其中target
仍然是类型T
,而propA
和propB
最终在之后是keyof T
检查undefined
。
好的,希望其中之一能有所帮助。祝你好运!