简体   繁体   English

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

[英]can I provide a default value for keyof parameter in typescript?

Trying to enforce an optional, defaulted parameter on a generic method that must be a property of the passed object.尝试在泛型方法上强制使用可选的默认参数,该参数必须是传递对象的属性。 Here is an example:下面是一个例子:

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

But, in the above example, I would like to default propA and propB to foo and bar for example:但是,在上面的例子中,我想默认propApropBfoobar例如:

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

But when I do that, it complains that Type '"bar"' is not assignable to type 'keyof T'.ts(2322) which makes sense.但是当我这样做时,它抱怨Type '"bar"' is not assignable to type 'keyof T'.ts(2322)这是有道理的。 Even if I add a default type to T , it still doesn't work:即使我向T添加默认类型,它仍然不起作用:

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

Basically, I want to be able to do:基本上,我希望能够做到:

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'

Any ways of doing this?有没有办法做到这一点?

You can't easily use default parameter values for this without running afoul of the type checker.在不与类型检查器发生冲突的情况下,您不能轻松地为此使用默认参数值。

The simplest way to get what you're looking for is probably to use overloads to describe the different intended ways to call doIt() , like this:获得所需内容的最简单方法可能是使用重载来描述调用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
}

That works the way you want.这以您想要的方式工作。 Note that the three call signatures correspond to different numbers of parameters, and they each have different constraints on the generic type parameter T .请注意,三个调用签名对应于不同数量的参数,并且它们每个对泛型类型参数T都有不同的约束 (You may wish to use string instead of any as property types, there... not sure) (您可能希望使用string而不是any作为属性类型,那里......不确定)

Note that in the implementation signature I've got target as any , propA , and propB as (keyof any) | undefined请注意,在实现签名中,我将target作为anypropApropB作为(keyof any) | undefined (keyof any) | undefined . (keyof any) | undefined So the implementation won't be particularly type-safe here.所以这里的实现不会是特别类型安全的。 Overloads don't really help with implementation type safety;重载并不能真正帮助实现类型安全; they are more for the caller's benefit.他们更多的是为了呼叫者的利益。 If you tried to make the implementation generic you'd run into similar problems where the compiler can't verify that "foo" is assignable to keyof T , etc.如果您试图使实现通用,您会遇到类似的问题,编译器无法验证"foo"是否可分配给keyof T等。

Also note that we can't use default parameter values in overload signatures, but that's fine... we can just do that assignment ourselves inside the function implementation.还要注意,我们不能在重载签名中使用默认参数值,但这很好……我们可以在函数实现中自己进行赋值。 If the parameters are undefined , reassign them.如果参数undefined ,则重新分配它们。


Another way to do this is to use TypeScript's support for treating rest parameters as tuple types , including optional tuple elements :另一种方法是使用 TypeScript 支持将其余参数视为元组类型,包括可选的元组元素

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
}

This works similarly to the overloads.这与重载类似。 It's fairly ugly looking, which is not great.它看起来相当丑陋,这不是很好。 But, the implementation is actually relatively type-safe, where target is still known to be type T , and propA and propB end up being keyof T after checking against undefined .但是,该实现实际上是相对类型安全的,其中target仍然已知为类型T ,并且propApropB在检查undefined后最终成为keyof T


Okay, hope one of those helps.好的,希望其中之一有帮助。 Good luck!祝你好运!

Link to code 代码链接

What about casting the default value?铸造默认值怎么样?

Simplified example简化示例

const doIt = <T = { id: string }>(key: keyof T = 'id' as keyof T) => {}

Example from question问题示例

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM