如何正确键入 typescript 中记录的关键字

yrdbyhpb  于 2023-01-14  发布在  TypeScript
关注(0)|答案(1)|浏览(144)

我有下面的函数,根据过滤函数过滤对象的 prop :

function filterProps<K extends string, V>(object: Record<K, V>, fn: (key:K, value: V, object: Record<K, V>) => unknown) {
  // return a new object with only the properties filtered by fn
  return Object.entries(object).reduce( 
    (acum, [key, value]) => fn(key, value, object) ? {...acum, [key]: value } : acum, 
    {} as Record<K, V>
  )
}

我得到错误:

(parameter) key: string
Argument of type 'string' is not assignable to parameter of type 'K'.
  'string' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string | number | symbol'.(2345)

应该如何键入"key"以避免该错误?
注:此处提供Playground

31moq8wy

31moq8wy1#

the Object.entries() static method的TypeScript类型为

interface ObjectConstructor {
    entries<T>(o: { [s: string]: T } | ArrayLike<T>): [string, T][];
    entries(o: {}): [string, any][];
}

这两个函数都返回一个键值元组数组,其中键的类型为string。没有得到K的原因是TypeScript中的对象类型不是"密封的"或"精确的"(如microsoft/TypeScript#12936中所要求的);一个对象总是有可能拥有比TypeScript所知道的更多的属性。有关Object.keys()返回string[]的类似问题/答案,请参见Why doesn't Object.keys return a keyof type in TypeScript?
例如,可能会出现以下情况:

interface Foo {
  bar: string,
  baz: string;
}
const x = { bar: "abc", baz: "def", qux: 123 };
const y: Foo = x; // okay

注意赋值const y: Foo = x是如何成功的;这是因为x包含了Foo中所需的所有属性,并且 * 多余的属性不会使赋值无效 *。对于像const y: Foo = { bar: "abc", baz: "def", qux: 123 }这样的 * 对象常量 *,存在多余的属性检查,但这会失败,因为关于qux的信息将完全被编译器遗忘(因此被认为是错误),而不是因为qux违反了任何东西。
这意味着filterProps()方法实现在技术上是不安全的,因为我可以写

filterProps(y, (k, v) => v.toUpperCase() === v) // compiles, but
// 💥 RUNTIME ERROR! v.toUpperCase is not a function

这错误地假设v将是string类型。调用编译没有错误,因为filterProps()的调用签名允许它,但随后实现最终将number视为string,并且您得到运行时错误。
所以才会有错误。你所做的在技术上是不安全的。
如果你确信你的对象实际上不会有这种意外类型的多余属性,那么你可以向编译器Assert,一种方法是AssertObject.entries(object)返回Array<[K, V]>,如下所示:

function filterProps<K extends string | symbol | number, V>(
  object: Record<K, V>, fn: (key: K, value: V, object: Record<K, V>) => unknown) {
 
  return (Object.entries(object) as Array<[K, V]>).reduce(
  // --------------------------> ^^^^^^^^^^^^^^^^
    (acum, [key, value]) => fn(key, value, object) ? { ...acum, [key]: value } : acum,
    {} as Record<K, V>
  )
}

现在实现编译无误了,因为编译器假设您知道自己在做什么,并且Object.entries(object)将返回一个Array<[K, V]>,正如您所声明的那样。
这并没有使函数变得更安全,因为您仍然可以调用filterProps(y, (k, v) => v.toUpperCase() === v),但是类型Assert将防止这种情况发生的责任从编译器转移到了您身上,所以继续操作要自担风险!
Playground代码链接

相关问题