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

Oli*_*Ash 2 generics typescript

在下面的示例中,我想不出任何分配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;
};
Run Code Online (Sandbox Code Playgroud)

TypeScript 游乐场示例

jca*_*alz 5

@TitianCernicovaDragomir 本质上是正确的,编译器通常无法对未解析的泛型类型进行复杂的类型分析。它在具体类型上表现得更好。请参阅Microsoft/TypeScript#28884Pick有关此问题以及Omit互补键集的讨论。

在这些情况下,继续的唯一方法是您亲自验证分配是否正确,然后使用类型断言,partial = picked as Partial<T>......


...但在这种情况下我不会这样做。这里的错误确实是一个很好的错误,尽管很难看出原因,因为您基本上只是覆盖了变量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; // 
};
Run Code Online (Sandbox Code Playgroud)

所以,基本问题是 是Pick<T, K>比更广泛的类型TT它包含键为 in 的属性K,但不知道是否包含键不在中的属性K。我的意思是,类型的值Pick<{a: string, b: number}, "a">很可能有一个b属性。如果它确实有一个,则它不必是 类型numberPick<T, K>所以将 type 的值赋给type 的变量是错误的Partial<T>

让我们用一个愚蠢的例子来充实这一点。假设您有一个Tree接口和一个类型为 的对象Tree,如下所示:

interface Tree {
  type: string;
  age: number;
  bark: string;
}
  
const tree: Tree = {
  type: "Aspen",
  age: 100,
  bark: "smooth"
};
Run Code Online (Sandbox Code Playgroud)

并且您还有一个Dog接口和一个类型为 的对象Dog,如下所示:

interface Dog {
  name: string;
  age: number;
  bark(): void;
}

const dog: Dog = {
  name: "Spot",
  age: 5,
  bark() {
    console.log("WOOF WOOF!");
  }
};
Run Code Online (Sandbox Code Playgroud)

因此,dogtree都有一个数字age属性,并且它们都有bark不同类型的属性。一个是a string,另一个是方法。请注意,这dog是一个完全有效的 type 值Pick<Tree, "age">,但却是一个无效的type 值Partial<Tree>。因此,当您致电时fn()

const partialTree = fn<Tree, "age">(tree, dog); // no error
Run Code Online (Sandbox Code Playgroud)

我的修改fn()返回dogPartial<Tree>,有趣的开始:

if (partialTree.bark) {
  partialTree.bark.toUpperCase(); // okay at compile time
  // at runtime "TypeError: partialTree.bark.toUpperCase is not a function"
}
Run Code Online (Sandbox Code Playgroud)

这种不健全性的泄漏正是因为Pick<T, K>不知道排除或以其他方式限制“未采摘”的属性。您可以创建自己的属性,其中明确排除来自not inStrictPicked<T, K>的属性:TK

type StrictPicked<T, K extends keyof T> = Pick<T, K> &
  Partial<Record<Exclude<keyof T, K>, never>>;
Run Code Online (Sandbox Code Playgroud)

现在你的代码更加健全(忽略像上面评论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;
};
Run Code Online (Sandbox Code Playgroud)

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

fn2<Tree, "age">(tree, dog); // error, dog is not a StrictPicked<Tree, "age">
Run Code Online (Sandbox Code Playgroud)

链接到代码