typescript 泛型用作对象键的问题

x4shl7ld  于 2023-10-22  发布在  TypeScript
关注(0)|答案(2)|浏览(105)

我使用了一个带有泛型的变量作为对象键,它似乎不起作用。这里我有一个非常简单的例子,我有两个对象(我使用declare告诉TS那里有东西,而不必创建对象),它们都共享一些相同的键,但它们都有键,另一个没有。然后我有一个泛型函数,它的类型必须是两个对象的键。然后我使用键将一个对象的值赋给另一个对象,但是TypeScript不喜欢它,特别是被赋给的对象。但我不知道为什么,因为键必须是两个对象的键,而且因为我使用的是泛型,它应该知道这是可以的,因为它们可以相互赋值。
如果:

  • 如果被指定的对象没有其他对象没有的关键点。
  • 它们共享的所有键都具有相同的类型,因为它们共享键“B”、“c”、“d”,并且这些键的值都是数字。
  • 当它不是仿制药时,如底部所示。

这里有一个例子:

// these don't matter, it only matters they aren't assignable to each other (strictNullCheck: true, null is not assignable to undefined)
type A = number
type B = string
type C = boolean
type D = null
type E = undefined

type Test1 = {
    a: A
    b: B
    c: C
    d: D
}
declare const test1: Test1

type Test2 = {
    b: B
    c: C
    d: D
    e: E
}
declare const test2: Test2

function testFn<T extends keyof Test1 & keyof Test2>(t: T) {
    test1[t] = test2[t]
//  ^^^^^^^^
    // Type 'Test2[T]' is not assignable to type 'Test1[T]'.
    // Property 'a' is missing in type 'Test2' but required in type 'Test1'. ts(2322)
}

const key = 'b'
test1[key] = test2[key] // works fine with b, c, d
i5desfxk

i5desfxk1#

问题:

testFn函数的问题在于它不是类型安全的。T类型参数被限制为Test1和Test2的键,但Test2没有属性a。这意味着行test1[t] = test2[t]将无法编译,并显示错误消息Type 'Test2[T]' is not assignable to type 'Test1[T]'。类型“Test2”中缺少属性“a”,但类型“Test1”中需要属性“a”

修复此问题:

1.将类型参数T更改为Test2的keyof。这将允许该函数与Test2的任何属性一起使用,但它对Test1不再是类型安全的。
1.在函数中添加类型保护,以检查t参数是否是Test1的键。如果是,则函数可以安全地将test2[t]分配给test1[t]。
1.若要将值赋给类型中可能不存在的属性,可以使用?operator。该操作符将告诉TypeScript该属性是可选的。

解决方案

// type guard:

function testFn<T extends keyof Test1 & keyof Test2>(t: T): void {
          if (t in Test1) {
            test1[t] = test2[t];
          }
        }


 //using as :
     function testFn<T extends keyof Test2>(t: T): void {
          test1[t] = test2[t] as Test1[T] | undefined;
        }

输出

testFn('b'); // works fine
testFn('c'); // works fine
testFn('d'); // works fine
testFn('a'); // works fine, but the value of `test1.a` will be `undefined`
pwuypxnk

pwuypxnk2#

这是microsoft/TypeScript#32693中描述的已知问题。
TypeScript不允许你将一个索引访问分配给另一个索引访问,比如o1[k1] = o2[k2],即使对象和键是相同的类型。这是因为单靠类型不足以保证安全;如果键是像"b" | "c" | "d"这样的联合类型,那么你可能会意外地让k1成为"b",而k2成为"c"。这种类型的安全加强在microsoft/TypeScript#30769中实现,虽然它确实有助于防止错误,但不幸的是,它也阻止了许多完全安全的任务。具体来说,当你有o1[k] = o2[k]时,赋值两边的键不仅是相同的 type,而且实际上是相同的 value,那么它应该是安全的。但是TypeScript目前并不跟踪键的身份,只是跟踪它们的类型。
目前唯一允许的方法是使用泛型键(您已经在这样做了)和相同类型的对象(这部分是它对您的破坏),如ms/TS#30769中的此注解所述。因此,如果您可以将至少一个对象表示为具有与另一个对象兼容的类型,则可以修复错误。举例来说:

function testFn<T extends keyof Test1 & keyof Test2>(t: T) {
  const t1: Omit<Test1, "a"> = test1
  t1[t] = test2[t]
}

这是使用Omit实用程序类型将test1Test1安全扩展到Omit<Test1, "a">,并且我们使用类型注解而不是类型Assert,以便编译器在不安全时警告我们。然后t1[t] = test2[t]工作,因为它认为test2也是Omit<Test1, "a">类型。
如果这是自动发生的,那就太好了,但事实并非如此。如果t的标识对编译器很重要,那也很好,但事实并非如此。即使是变通方法也不一定安全,因为您可以对k1k2进行相同的练习,其中两者都是通用的。因此,在所有这一切是一个很多箍跳跃,使一些大多数人希望没有它的工作。但这是目前的情况。
Playground链接到代码

相关问题