我可以在打字稿中提供keyof参数的默认值吗?

时间:2019-07-22 17:47:44

标签: typescript

试图在通用方法上强制使用一个可选的默认参数,该参数必须是所传递对象的属性。这是一个示例:

function doIt<T>(target: T, propA: keyof T, propB: keyof T): string {
    return `${target[propA]} ${target[propB]}`;
}

但是,在上面的示例中,我想将propApropB默认设置为foobar,例如:

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'

有什么办法吗?

1 个答案:

答案 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称为anypropApropB称为(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,而propApropB最终在之后是keyof T检查undefined


好的,希望其中之一能有所帮助。祝你好运!

Link to code

相关问题