没有 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

我要找的类型应该是:

这可能吗?


编辑:我还在 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 无法正确区分 缺少 的对象属性(和函数参数) 存在,但 undefinedPartial<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 对更高种类类型的支持不是很好。我不知道这是否真的适用于您的用例,但这是我目前使用该语言所能做的最好的事情。

Playground link to code

在这种情况下,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

Playground link