将通用对象分配给Partial类型

时间:2020-02-28 17:31:34

标签: typescript

过一会儿,我再次使用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'

的参数

在保持正确输入的情况下无法理解如何解决此问题。

1 个答案:

答案 0 :(得分: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视为一个对象,结果很奇怪:< / p>

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<Record<K, number>>类型。在任何情况下,字符串索引签名都不好,并且会破坏您所看到的东西。


另一个绊脚石是,即使编译器意识到根据通用类型TK可以将计算出的属性分配给某些合适的类型,它也可能无法识别出这种情况。类型可分配给Partial<T>。对于人类来说,这很容易理解,但是编译器不一定会推理出这种类型的高阶类型操作。与此相关的规范性公开问题可能是microsoft/TypeScript#28884,其中的问题是,在Pick<T, K> & Omit<T, K>T是通用的情况下,TK不兼容。对于任何静态已知的类型TK,编译器都可以进行分析:Pick<{a: string, b: string}, "a"> & Omit<{a: string, b: string}, "a">实际上被视为可分配给{a: string, b: string}。但是一旦将类型设为通用类型,编译器便会放弃。


由于这两个原因,几乎没有机会让编译器为您验证可分配性。我能提供的最佳建议是使用该类型声明并继续。

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

Playground link to code