【问题标题】:Assignment of generic object to Partial type将泛型对象分配给 Partial 类型
【发布时间】:2020-06-12 20:24:31
【问题描述】:

过了一会儿,我又开始使用 TypeScript,但我被这个问题困住了。

export class CrossBrowserStorage<T> {
  getValue<P extends keyof T>(
    key: P,
    defaultValue: T[P]
  ): Observable<T[P]> {
    return this.getValues({ [key]: defaultValue }).pipe(map(values => values[key]));
                      ----^ TS2345: Argument of type '{ [x: string]: T[P]; }' is not assignable 
                            to parameter of type 'Partial '
  }

  getValues(keys: Partial<T>): Observable<Partial<T>> {
    return from(browser.storage.sync.get(keys) as Promise<Partial<T>>);
  }
}

TS2345:'{ [x: string]: T[P]; }' 类型的参数不可分配给 'Partial'类型的参数

无法理解如何在保持正确输入的同时解决此问题。

【问题讨论】:

  • 这是this questionthis question 混合的副本:计算的密钥扩大到string;即使您将其缩小到P,编译器仍然无法在通用情况下进行验证。您需要使用像{ [key]: defaultValue } as Pick&lt;T, P&gt; as Partial&lt;T&gt; 这样的类型断言。如果其他答案没有涵盖您的问题,请告诉我

标签: typescript


【解决方案1】:

很遗憾,编译器无法为您验证这一点,在这种情况下,您对编译器了解得更多,一个合理的解决方案是使用type assertion

class Class<T> {
    f<K extends keyof T>(k: K, v: T[K]) {
        const badPartial: Partial<T> = { [k]: v }; // error!
        const goodPartial: Partial<T> = { [k]: v } as Pick<T, K> & Partial<T>; // okay
    }
}

所以这就是你需要做的。但是为什么会这样呢?编译器似乎面临两个主要的绊脚石。


第一个绊脚石是,如果键是单个静态已知的文字或唯一符号类型,编译器只知道如何解释计算属性:

const k1 = "a";
const o1 = { [k1]: 123 }; 
// const o1: {[k1]: number};
o1.a; // okay
o1.b; // error

相反,如果键类型是泛型或字符串文字的并集,编译器会将其扩展为 string 并将生成的对象视为带有 string index signature 的对象,结果很奇怪:

const k2 = Math.random() < 0.5 ? "a" : "z";
const o2 = { [k2]: 123 };
// const o2: {[x: string]:number};
o2.a; // no error, but might not exist
o2.b; // no error, but *definitely* doesn't exist

function foo<K extends string>(k3: K) {
    const o3 = { [k3]: 123 };
    // const o3: {[x: string]:number};
    o3.a; // no error, but probably doesn't exist
}

这是 TypeScript 中的一个未解决问题;见microsoft/TypeScript#13948。对于文字联合的情况,应该做的“正确的事情”是什么,这一点很清楚。大概上面的o2 应该是{a: number} | {z: number} 类型。泛型不太清楚。也许上面的o3 应该是Partial&lt;Record&lt;K, number&gt;&gt; 类型。在任何情况下,字符串索引签名都不是很好,并且会破坏您所看到的内容。


另一个绊脚石是,即使编译器意识到根据泛型类型TK 可以将计算属性分配给某个合适的类型,它也可能无法识别该类型可以分配给Partial&lt;T&gt;。人类很容易看到这一点,但编译器不一定能推断出这种高阶类型操作。关于此的规范公开问题可能是microsoft/TypeScript#28884,其中问题是Pick&lt;T, K&gt; &amp; Omit&lt;T, K&gt; 不被视为与T 兼容,而TK 是通用的。对于任何静态已知类型TK,编译器可以进行分析:Pick&lt;{a: string, b: string}, "a"&gt; &amp; Omit&lt;{a: string, b: string}, "a"&gt; 确实被视为可分配给{a: string, b: string}。但是一旦你把类型变成泛型,编译器基本上就放弃了。


由于这两个原因,让编译器为您验证可分配性的机会很小。我能给出的最佳建议是使用该类型断言并继续前进。

好的,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

  • 非常感谢!这是一个超级完整的答案。我不特别喜欢在不需要的时候添加代码,但是,嘿,这次没关系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-22
相关资源
最近更新 更多