在下面的示例中,我无法想到将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;
};
答案 0 :(得分:2)
@TitianCernicovaDragomir本质上是正确的,因为编译器通常无法对未解析的泛型类型进行复杂的类型分析。对于具体类型,它的性能要好得多。有关Pick
和Omit
以及互补键集的讨论,请参见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;
};
无论如何,希望能有所帮助。祝你好运!