我有两个函数,foo1
和foo2
,它们几乎相同,只是后者是泛型的。
type MyType = { a: number }
type PartialWithBar<T> = Partial<T> & { bar: boolean }
export const foo1 = (obj: MyType) => {
const result: PartialWithBar<MyType> = { bar: false }
Object.assign(result, obj)
return result
}
export const foo2 = <T extends { [key: string]: unknown }>(obj: T) => {
const result: PartialWithBar<T> = { bar: false }
Object.assign(result, obj)
return result
}
foo1
没有给出任何错误,但在foo2
中我得到以下错误:
Type '{ bar: false; }' is not assignable to type 'PartialWithBar<T>'.
Type '{ bar: false; }' is not assignable to type 'Partial<T>'.ts(2322)
为什么我会收到此错误?我希望result
具有属性bar
它应该是布尔值,并且我希望它可以选择具有T
的属性,但我似乎无法让它工作。
我知道我可以只使用类型断言(const result = { bar: false } as PartialWithBar<T>
),但如果可能的话,我不想这样做。
为了使一个值可分配给交集类型Partial<T> & {bar: boolean}
,它必须可分配给两种类型,因为交集对应于逻辑"and"。{bar: false}
的值可分配给{bar: boolean}
,但不一定可分配给Partial<T>
。
后者取决于T
是什么。特别是,当T = {bar: number}
时,类型PartialWithBar<T>
实际上是never
:
type Example = PartialWithBar<{bar: number}>
// type Example = never
所以 Typescript 是正确的:你的结果不一定可以分配给这个类型,因为T
可能是使它不可分配的东西。
幸运的是,这里有一个很好的解决方案可以避免类型断言:Object.assign
本身返回一个交集类型,并且 Typescript 能够知道T
可分配给Partial<T>
。
export const foo2 = <T extends object>(obj: T): PartialWithBar<T> => {
return Object.assign({bar: false}, obj);
}
但是,请注意,虽然Object.assign
的返回类型很方便,但出于与上面讨论的原因相同的原因,它通常不合理。为了提高安全性,您还可以从类型T
中删除属性bar
,以防存在。请注意,上面的代码与您的原始代码一样,如果bar
属性有属性,则会阻止它obj
,因此正确的类型如下所示:
type PartialWithBar<T> = Partial<T> & Omit<{bar: boolean}, keyof T>
游乐场链接
相反,如果您希望在bar
属性存在时覆盖它,则应改为编写以下内容:
type PartialWithBar<T> = Partial<Omit<T, 'bar'>> & { bar: boolean }
export const foo2 = <T extends object>(obj: T): PartialWithBar<T> => {
return Object.assign({}, obj, {bar: false})
}
游乐场链接
为我的问题添加另一个答案:
export const foo2 = <T extends { [key: string]: unknown }>(obj: T) => {
const part: Partial<T> = {};
const result: PartialWithBar<T> = { ...part, bar: false };
// Optionally do stuff e.g. Object.assign(result, obj)
return result;
};
鉴于这个答案也有效,我仍然不相信 TypeScript 中没有导致我最初发布的错误的错误。