没有未定义的 TypeScript Partial<T> 类型

Posted

技术标签:

【中文标题】没有未定义的 TypeScript Partial<T> 类型【英文标题】:TypeScript Partial<T> type without undefined 【发布时间】:2019-02-02 03:51:46 【问题描述】:

如何创建一个 kinda-Partial&lt;T&gt; 类型,不允许 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&lt;T&gt;) 接受泛型键的子集 如果泛型类型不接受 undefined 作为该键,则不接受 undefined

这可能吗?


编辑:我还在 TypeScript 存储库中创建了一个问题,因为这很奇怪,应该在某个时候抛出错误:https://github.com/Microsoft/TypeScript/issues/29701

【问题讨论】:

请记住,这样做可能会使您的代码更难以编程方式使用;你会禁止例如update(value: MyType, new_foo_val?: string) return merge(value, foo: new_foo_val ). 【参考方案1】:

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 --exactOptionalPropertyTypes;由于某种原因,网址已损坏

——--

PRE-TS4.4 答案:

TypeScript 无法正确区分 缺少 的对象属性(和函数参数)与 存在但 @987654332 的对象属性(和函数参数)是一个已知限制(参见 microsoft/TypeScript#13195) @Partial&lt;T&gt; 允许 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&lt;T&gt;。但是您可以编写一个类型 VerifyKindaPartial&lt;T, KP&gt;,它接受一个类型 T 和一个 candidate 类型 KP,您想检查您想要的 KindaPartial&lt;T&gt;。如果候选匹配,则返回匹配KP 的内容。否则它会返回不存在的东西。

然后,您将merge() 设为generic 函数,该函数从传递给value2 的值的类型推断KP。如果KP &amp; VerifyKindaPartial&lt;MyType, KP&gt; 匹配KP(意味着KP 匹配KindaPartial&lt;MyType&gt;),则代码将编译。否则,如果KP &amp; VerifyKindaPartial&lt;MyType, KP&gt; not 匹配KP(意味着KP 不匹配KindaPartial&lt;MyType&gt;),则会出现错误。 (不过,错误可能不是很直观)。

让我们看看:

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 &amp; string,即never。哦,好吧。

无论如何,这里可能有一些警告;泛型函数在直接调用时运行良好,但由于 TypeScript 对更高种类的类型的支持不是很好,所以它们不能很好地组合。我不知道这是否真的适用于您的用例,但这是我能用目前的语言做的最好的事情。

Playground link to code

【讨论】:

感谢您的解释!我对这个问题竖起了大拇指。自从 TypeScript 和 Partial 出现以来,我一直在使用它,但这是我第一次偶然发现这个问题。 因为它需要所有的属性,而且根本不是局部的? 不,请参阅documentation for mapped types。 [P in K]: XK 的每个联合成员都有一个属性。 您的KindaMyTypeMyType 的类型相同。您可以(不安全地)使用类型断言(您称之为“强制转换”)将bar: 666 缩小到MyType 这一事实与接受部分MyType 的函数不同。我很惊讶你没有在你的 cmets 中提到类型断言,因为这是你解决方法的关键。【参考方案2】:

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

【讨论】:

以上是关于没有未定义的 TypeScript Partial<T> 类型的主要内容,如果未能解决你的问题,请参考以下文章

在 TypeScript 中,当类型是函数的参数时,有没有办法限制 Partial<T> 类型的额外/多余属性?

当没有要指定的确切值“未定义”或“空”时,为 TypeScript 可选参数传递啥? [复制]

Typescript的高级tricks(in,keyof,Partial,Pick,Exclude等)

typescript-eslint 配置:.eslintrc 文件“模块”未定义

AngularJS 和 Typescript - 注入其他服务的服务未定义

如何在 TypeScript 中定义一个 Partials 数组?