【问题标题】:How to prevent `Required<T>` in typescript from removing 'undefined' from the type when using --strictNullChecks使用--strictNullChecks时如何防止打字稿中的`Required<T>`从类型中删除'undefined'
【发布时间】:2019-12-10 22:54:20
【问题描述】:

Typescript 允许您使用 -? 映射类型修饰符删除“可选项性”(?),以便与 Required&lt;T&gt; 类型一起使用。

type Required<T> = { [P in keyof T]-?: T[P] };

但是在使用--strictNullChecks 时注意following 很重要(就像我一样)。

注意,在 --strictNullChecks 模式下,当同态映射类型 删除一个?来自基础类型中的属性的修饰符 从该属性的类型中删除 undefined:

我正在寻找绕过这种副作用的方法...

即我想删除? 但如果|undefined 存在我想保留它

为什么?

我有服务器生成的接口,这些接口具有可选的 (?) 属性。我想重构我的服务器代码并添加/删除成员。因此,我想要一种方法,无论是否需要,我都必须为每个属性显式设置一个值(或显式设置为 undefined)。

困境:

  • 如果我使用Required&lt;T&gt;,那么它会吞噬我的| undefined,但前提是还存在?

因此,以下内容颇具讽刺意味:

export type RequiredDog = Required <{

    bark: 'loud' | 'quiet' | undefined,
    bite?: 'nip' | 'clamp' | undefined
}>; 

居然变成了这样:

type RequiredDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp";
}

所以bitebark 更可选,现在实际上更少可选!

有没有办法在不删除undefined 的情况下删除?

【问题讨论】:

  • 我重读了你的“为什么?”段落四次,我仍然不明白为什么重构您的服务器代码添加/删除成员会导致您希望必须显式编写具有值undefined 的属性的结论。这些属性是可选的。所以...? (有时,如果“如何”问题很清楚,“为什么”并不重要[就像这里一样]。但在这里我认为理解“为什么”可能会导致问题的最终答案,这可能并不完全“如何”要求... :-) )
  • @T.J.Crowder 归结为几件事。仅仅因为某些东西在一天结束时(在 API 中)是“可选的”,并不意味着您不必考虑价值的来源。假设我添加了Address3 - 当然这是可选的 - 但如果我实际使用它,我需要记住设置它。如果我把它重命名为AddressLine3。它变得更加棘手。我想要的只是确保我没有忘记任何东西。 API 可能会改变,我希望编译器尽可能多地告诉我。当我添加一些东西或重命名它并且没有额外的打字错误时,我会感到紧张!...
  • 在 .NET 和 typescript/JS 工作之间存在一些微妙之处,首先是必需的/可为空的/未定义的意思,这肯定会使事情复杂化。我正在使用 Swagger 代码生成,我可以使用 [JsonRequired] 来防止生成 ?。那么问题是,虽然'null'与打字稿中的undefined不同,但.NET服务器并没有做出这种区分。因此,如果我发送 null 它会爆炸。所以它必须是可选的。
  • 我遇到的实际问题(导致我写这个问题)是我有一些源自服务器的字段,通过Pick&lt;T&gt; 映射到另一种类型,然后最终发送回服务器.如果它们是可选的(因为 .NET 不匹配),那么很容易忘记将每个字段复制回来。在正确的位置插入我的答案中的SmartRequired&lt;T&gt; 突然揭示了我所缺少的东西。 (我现在可以睡觉了!)

标签: typescript mapped-types


【解决方案1】:

如果我试图实现一个 Required&lt;&gt; 不会从属性中剥离 undefined,我可能会这样做:

type RequiredKeepUndefined<T> = { [K in keyof T]-?: [T[K]] } extends infer U
  ? U extends Record<keyof U, [any]> ? { [K in keyof U]: U[K][0] } : never
  : never;

我在这里用一堆mappedconditional 类型做的是将值类型包装在一个单元组中,使其成为Required,然后解开单元组。这在精神上与您的答案相似,但如果有人在属性中使用 "undefined" 作为字符串文字,它不会与发生的事情发生冲突。

你可以看到它的行为如你所愿:

interface Dog {
    bark: "loud" | "quiet" | undefined;
    bite?: "nip" | "clamp" | undefined;
}

type RequiredKeepUndefinedDog = RequiredKeepUndefined<Dog>
/* type RequiredKeepUndefinedDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp" | undefined;
} */

你可以把它想象成下面的变换

  • {bark: "loud" | "quiet" | undefined, bite?: "nip" | "clamp" | undefined}
  • {bark: ["loud" | "quiet" | undefined], bite?: ["nip" | "clamp" | undefined] | undefined}
  • {bark: ["loud" | "quiet" | undefined], bite: ["nip" | "clamp" | undefined]}
  • {bark: "loud" | "quiet" | undefined, bite: "nip" | "clamp" | undefined}

好的,希望对您有所帮助;祝你好运!

Link to code

【讨论】:

  • 这太好了 - 谢谢。当我注意到在Required之后“咬”是“不那么可选”时,我就对自己说,一定有更好的方法!
【解决方案2】:

我仍然喜欢更清洁的解决方案,但以下方法似乎可行:

我基本上创建了一个名为 'undefined' 的类型,我用这两个替换/取消替换真正的 undefined 类型:

type WrapUndefined<T> = {
    [P in keyof T]: undefined extends T[P] ? 'undefined' | T[P] : T[P];
};

type UnwrapUndefined<T> = {
    [P in keyof T]: 'undefined' extends T[P] ? Diff<T[P], 'undefined'> | undefined : T[P];
};

如果我有:

type Person = {
   firstName?: string;
}

然后WrapUndefined&lt;Person&gt; 成功

firstName?: string | undefined | 'undefined';     // *

然后您可以致电Required&lt;T&gt; 获取此信息:

firstName: string | 'undefined';    // 'undefined' doesn't get removed now

然后用UnwrapUndefined&lt;T&gt;反转得到

firstName: string | undefined;

然后我创建一个单一的类型来完成这一切:

export type SmartRequired<T> = UnwrapUndefined<Required<WrapUndefined<T>>>;

如果我在我原来的 Dog 类型上运行这个问题,我会得到我想要的:

export type RequiredDog = SmartRequired <{

    bark: 'loud' | 'quiet' | undefined,
    bite?: 'nip' | 'clamp' | undefined
}>; 

这是什么

type RequiredDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp" | undefined;
}

* 高级说明:此步骤在 VSCode 中显示为 string | undefined,因为它似乎将“未定义”吸收到 string 中。幸运的是,当您运行所有三个步骤时,它最终会得到正确的结果。

【讨论】:

  • 附言。如果您确实觉得这很有用 - 可能需要一个比 SmartRequired 更好的名称 :-)
  • 然后还有RequiredPick&lt;T, K extends keyof T&gt; = UnwrapUndefined&lt;Required&lt;Pick&lt;WrapUndefined&lt;T&gt;, K&gt;&gt;&gt;
  • 还有另一种情况,我们没有明确地将undefined 类型设置为条件属性:type Person = { firstName?: string } 您的方法输出在属性上注入了所需的undefined。有什么方法可以预防吗?
猜你喜欢
  • 2021-12-05
  • 2021-07-17
  • 1970-01-01
  • 2021-12-05
  • 1970-01-01
  • 2019-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多