TypeScript:将`Pick <T,K>`分配给`Partial <T>`

时间:2019-06-27 10:16:23

标签: typescript generics

在下面的示例中,我无法想到将Pick<Object, Key>分配给Partial<Object>的任何情况都是不合理的,因此我希望这是允许的。

任何人都可以澄清为什么不允许这样做吗?

const fn = <T, K extends keyof T>(partial: Partial<T>, picked: Pick<T, K>) => {
    /*
    Type 'Pick<T, K>' is not assignable to type 'Partial<T>'.
        Type 'keyof T' is not assignable to type 'K'.
            'keyof T' is assignable to the constraint of type 'K', but 'K' could be instantiated with a different subtype of constraint 'string | number | symbol'.
    */
    partial = picked;
};

TypeScript playground example

1 个答案:

答案 0 :(得分:2)

@TitianCernicovaDragomir本质上是正确的,因为编译器通常无法对未解析的泛型类型进行复杂的类型分析。对于具体类型,它的性能要好得多。有关PickOmit以及互补键集的讨论,请参见Microsoft/TypeScript#28884

在这种情况下,唯一的处理方法是让您亲自验证作业是否正确,然后像在partial = picked as Partial<T>中一样使用type assertion ...


...但是在这种情况下我不会这样做。该错误在这里确实是一个好错误,尽管很难理解为什么,因为您实际上只是覆盖了partial变量,并且在函数范围内不对其执行任何操作。因此,尽管声音不够完善,但由于它未被允许在其他地方造成严重破坏,因此该代码是无害的。让我们通过使fn()返回修改后的partial变量来解除链接:

const fn = <T, K extends keyof T>(partial: Partial<T>, picked: Pick<T, K>) => {
  partial = picked; // error, for good reason
  return partial; // ?
};

因此,基本问题是Pick<T, K>是比T更宽的类型。它包含T中带有键K中的属性,但是不知道 not 中包含{{1}中带有键 not 中的属性}。我的意思是,类型K的值很可能具有Pick<{a: string, b: number}, "a">属性。并且如果确实有一个,则不必是b类型。因此,将类型number的值赋给类型Pick<T, K>的变量是错误的。

让我们以一个愚蠢的例子充实它。假设您有一个Partial<T>接口和一个Tree类型的对象,如下所示:

Tree

您还拥有一个interface Tree { type: string; age: number; bark: string; } const tree: Tree = { type: "Aspen", age: 100, bark: "smooth" }; 接口和一个Dog类型的对象,如下所示:

Dog

因此,interface Dog { name: string; age: number; bark(): void; } const dog: Dog = { name: "Spot", age: 5, bark() { console.log("WOOF WOOF!"); } }; dog都具有数字tree属性,并且都具有不同类型的age属性。一个是bark,另一个是方法。请注意,string是类型dog的完全有效值,但是类型Pick<Tree, "age">无效值。因此,当您致电Partial<Tree>时:

fn()

我修改过的const partialTree = fn<Tree, "age">(tree, dog); // no error 返回fn()dog,开始有趣:

Partial<Tree>

这种不健全之所以泄漏是因为未知的if (partialTree.bark) { partialTree.bark.toUpperCase(); // okay at compile time // at runtime "TypeError: partialTree.bark.toUpperCase is not a function" } 排除或限制了“未选中”的属性。您可以创建自己的Pick<T, K>,其中明确排除StrictPicked<T, K>T以外的属性:

K

现在您的代码更加合理了(忽略type StrictPicked<T, K extends keyof T> = Pick<T, K> & Partial<Record<Exclude<keyof T, K>, never>>; 之类的怪异事物,就像above comment中的品牌类型一样)...但是编译器仍然无法验证它:

K

这仍然是这里的基本问题;编译器无法轻松处理这样的事情。也许有一天?但是至少在调用方这方面它不那么容易被滥用:

const fn2 = <T, K extends keyof T>(
  partial: Partial<T>,
  picked: StrictPicked<T, K>
) => {
  partial = picked; // also error
  partial = picked as Partial<T>; // have to do this
  return partial;
};

无论如何,希望能有所帮助。祝你好运!

Link to code