没有 undefined 的 TypeScript Partial<T> 类型
TypeScript Partial<T> type without undefined
如何创建一个 有点-Partial<T>
类型,它不允许 undefined
值?
这是一个例子:
interface MyType {
foo: string
bar?: number
}
const merge = (value1: MyType, value2: KindaPartial<MyType>): MyType => {
return {...value1, ...value2};
}
const value = {
foo: 'foo',
bar: 42
}
merge(value, {}); // should work
merge(value, { foo: 'bar' }); // should work
merge(value, { bar: undefined }); // should work
merge(value, { bar: 666 }); // should work
merge(value, { foo: '', bar: undefined }); // should work
merge(value, { foo: '', bar: 666 }); // should work
// now the problematic case:
merge(value, { foo: undefined }); // this should throw an error
// because MyType["foo"] is of type string
我要找的类型应该是:
- 只接受通用类型上存在的键(就像普通的
Partial<T>
)
- 接受通用类型键的子集
- 但 不接受
undefined
如果通用类型不接受该键的 undefined
这可能吗?
编辑:我还在 TypeScript 存储库中创建了一个问题,因为这很奇怪并且应该在某些时候抛出错误:https://github.com/Microsoft/TypeScript/issues/29701
TS 4.4 更新:
TS4.4 将有 an --exactOptionalPropertyTypes
compiler flag 来直接使用 Partial
为您提供您正在寻找的行为,只要您有意在您想要的地方添加 undefined
允许它:
interface MyType {
foo: string
bar?: number | undefined // <-- you want this
}
const merge = (value1: MyType, value2: Partial<MyType>): MyType => {
return { ...value1, ...value2 };
}
const value = {
foo: 'foo',
bar: 42
}
merge(value, {}); // okay
merge(value, { foo: 'bar' }); // okay
merge(value, { bar: undefined }); // okay
merge(value, { bar: 666 }); // okay
merge(value, { foo: '', bar: undefined }); // okay
merge(value, { foo: '', bar: 666 }); // okay
// now the problematic case:
merge(value, { foo: undefined }); // error!
// ----------> ~~~
// Type 'undefined' is not assignable to type 'string'
Playground link to code <-- 注意,目前您需要在 TS 配置选项卡中自行打开 --exactOptionalPropertyTypes
;由于某种原因,url 损坏了
————
TS4.4 之前的答案:
这是一个已知的限制(请参阅 microsoft/TypeScript#13195),即 TypeScript 无法正确区分 缺少 的对象属性(和函数参数) 存在,但 undefined
。 Partial<T>
允许 undefined
属性的事实是其结果。正确的做法是等到这个问题得到解决(如果你在 GitHub 中解决那个问题并给它一个或一个令人信服的用例的评论,这可能会更有可能)。
如果您不想等待,您可以使用以下 hacky 方式来获得类似这样的行为:
type VerifyKindaPartial<T, KP> =
Partial<T> & {[K in keyof KP]-?: K extends keyof T ? T[K] : never};
const merge = <KP>(value1: MyType, value2: KP & VerifyKindaPartial<MyType, KP>): MyType => {
return { ...value1, ...value2 };
}
所以不能直接写KindaPartial<T>
。但是你可以写一个类型VerifyKindaPartial<T, KP>
,它接受一个类型T
和一个候选类型KP
,你想根据你的预期KindaPartial<T>
.如果候选人匹配,那么它 returns 匹配 KP
。否则它 returns 没有的东西。
然后,您使 merge()
成为一个 generic 函数,它根据传递给 value2
的值的类型推断出 KP
。如果 KP & VerifyKindaPartial<MyType, KP>
匹配 KP
(意味着 KP
匹配 KindaPartial<MyType>
),那么代码将编译。否则如果KP & VerifyKindaPartial<MyType, KP>
确实不匹配KP
(意思是KP
不匹配KindaPartial<MyType>
),那么就会报错. (虽然错误可能不是很直观)。
让我们看看:
merge(value, {}); // works
merge(value, { foo: 'bar' }); // works
merge(value, { bar: undefined }); // works
merge(value, { bar: 666 }); // works
merge(value, { foo: '', bar: undefined }); // works
merge(value, { foo: '', bar: 666 }); // works
merge(value, { foo: undefined }); // error!
// ~~~ <-- undefined is not assignable to never
// the expected type comes from property 'foo',
这有你想要的行为......虽然你得到的错误有点奇怪(理想情况下它会说 undefined
不能分配给 string
,但问题是编译器知道传入的类型是 undefined
,它希望类型是 string
,因此编译器将这些与 undefined & string
相交,即 never
。哦,好吧。
无论如何,这里可能有一些注意事项;泛型函数在直接调用时效果很好,但它们组合得不好,因为 TypeScript 对更高种类类型的支持不是很好。我不知道这是否真的适用于您的用例,但这是我目前使用该语言所能做的最好的事情。
在这种情况下,Pick
应该可以。
interface MyType {
foo: string
bar?: number
}
const merge = <K extends keyof MyType>(value1: MyType, value2: Pick<MyType, K>): MyType => {
return {...value1, ...value2};
}
merge(value, {}); // ok
merge(value, { foo: 'bar' }); // ok
merge(value, { bar: undefined }); // ok
merge(value, { bar: 666 }); // ok
merge(value, { foo: '', bar: undefined }); // ok
merge(value, { foo: '', bar: 666 }); // ok
merge(value, { foo: undefined }); // ng
如何创建一个 有点-Partial<T>
类型,它不允许 undefined
值?
这是一个例子:
interface MyType {
foo: string
bar?: number
}
const merge = (value1: MyType, value2: KindaPartial<MyType>): MyType => {
return {...value1, ...value2};
}
const value = {
foo: 'foo',
bar: 42
}
merge(value, {}); // should work
merge(value, { foo: 'bar' }); // should work
merge(value, { bar: undefined }); // should work
merge(value, { bar: 666 }); // should work
merge(value, { foo: '', bar: undefined }); // should work
merge(value, { foo: '', bar: 666 }); // should work
// now the problematic case:
merge(value, { foo: undefined }); // this should throw an error
// because MyType["foo"] is of type string
我要找的类型应该是:
- 只接受通用类型上存在的键(就像普通的
Partial<T>
) - 接受通用类型键的子集
- 但 不接受
undefined
如果通用类型不接受该键的undefined
这可能吗?
编辑:我还在 TypeScript 存储库中创建了一个问题,因为这很奇怪并且应该在某些时候抛出错误:https://github.com/Microsoft/TypeScript/issues/29701
TS 4.4 更新:
TS4.4 将有 an --exactOptionalPropertyTypes
compiler flag 来直接使用 Partial
为您提供您正在寻找的行为,只要您有意在您想要的地方添加 undefined
允许它:
interface MyType {
foo: string
bar?: number | undefined // <-- you want this
}
const merge = (value1: MyType, value2: Partial<MyType>): MyType => {
return { ...value1, ...value2 };
}
const value = {
foo: 'foo',
bar: 42
}
merge(value, {}); // okay
merge(value, { foo: 'bar' }); // okay
merge(value, { bar: undefined }); // okay
merge(value, { bar: 666 }); // okay
merge(value, { foo: '', bar: undefined }); // okay
merge(value, { foo: '', bar: 666 }); // okay
// now the problematic case:
merge(value, { foo: undefined }); // error!
// ----------> ~~~
// Type 'undefined' is not assignable to type 'string'
Playground link to code <-- 注意,目前您需要在 TS 配置选项卡中自行打开 --exactOptionalPropertyTypes
;由于某种原因,url 损坏了
————
TS4.4 之前的答案:
这是一个已知的限制(请参阅 microsoft/TypeScript#13195),即 TypeScript 无法正确区分 缺少 的对象属性(和函数参数) 存在,但 undefined
。 Partial<T>
允许 undefined
属性的事实是其结果。正确的做法是等到这个问题得到解决(如果你在 GitHub 中解决那个问题并给它一个或一个令人信服的用例的评论,这可能会更有可能)。
如果您不想等待,您可以使用以下 hacky 方式来获得类似这样的行为:
type VerifyKindaPartial<T, KP> =
Partial<T> & {[K in keyof KP]-?: K extends keyof T ? T[K] : never};
const merge = <KP>(value1: MyType, value2: KP & VerifyKindaPartial<MyType, KP>): MyType => {
return { ...value1, ...value2 };
}
所以不能直接写KindaPartial<T>
。但是你可以写一个类型VerifyKindaPartial<T, KP>
,它接受一个类型T
和一个候选类型KP
,你想根据你的预期KindaPartial<T>
.如果候选人匹配,那么它 returns 匹配 KP
。否则它 returns 没有的东西。
然后,您使 merge()
成为一个 generic 函数,它根据传递给 value2
的值的类型推断出 KP
。如果 KP & VerifyKindaPartial<MyType, KP>
匹配 KP
(意味着 KP
匹配 KindaPartial<MyType>
),那么代码将编译。否则如果KP & VerifyKindaPartial<MyType, KP>
确实不匹配KP
(意思是KP
不匹配KindaPartial<MyType>
),那么就会报错. (虽然错误可能不是很直观)。
让我们看看:
merge(value, {}); // works
merge(value, { foo: 'bar' }); // works
merge(value, { bar: undefined }); // works
merge(value, { bar: 666 }); // works
merge(value, { foo: '', bar: undefined }); // works
merge(value, { foo: '', bar: 666 }); // works
merge(value, { foo: undefined }); // error!
// ~~~ <-- undefined is not assignable to never
// the expected type comes from property 'foo',
这有你想要的行为......虽然你得到的错误有点奇怪(理想情况下它会说 undefined
不能分配给 string
,但问题是编译器知道传入的类型是 undefined
,它希望类型是 string
,因此编译器将这些与 undefined & string
相交,即 never
。哦,好吧。
无论如何,这里可能有一些注意事项;泛型函数在直接调用时效果很好,但它们组合得不好,因为 TypeScript 对更高种类类型的支持不是很好。我不知道这是否真的适用于您的用例,但这是我目前使用该语言所能做的最好的事情。
在这种情况下,Pick
应该可以。
interface MyType {
foo: string
bar?: number
}
const merge = <K extends keyof MyType>(value1: MyType, value2: Pick<MyType, K>): MyType => {
return {...value1, ...value2};
}
merge(value, {}); // ok
merge(value, { foo: 'bar' }); // ok
merge(value, { bar: undefined }); // ok
merge(value, { bar: 666 }); // ok
merge(value, { foo: '', bar: undefined }); // ok
merge(value, { foo: '', bar: 666 }); // ok
merge(value, { foo: undefined }); // ng