使用< T>--strictNullChecks时,如何防止typescript中的“Required”从类型中删除“undefined”

nbnkbykc  于 2023-05-19  发布在  TypeScript
关注(0)|答案(3)|浏览(197)

Typescript允许您使用-?Map类型修饰符删除“可选性”(?),可以轻松地与Required<T>类型一起使用。

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

但是,在使用--strictNullChecks时(就像我一样),注意following是很重要的。
请注意,在--strictNullChecks模式下,当同态Map类型删除一个?修饰符,它也会从该属性的类型中删除undefined:

我正在寻找一种方法来绕过这种副作用

即,我想删除?,但如果|undefined存在,我想保留它
为什么?
我有服务器生成的接口,这些接口具有可选的(?)属性。我想重构我的服务器代码并添加/删除成员。因此,我想要一种方法,这样我就必须为每个属性显式地设置一个值(或显式地设置为undefined),无论是否需要。
两难:

  • 如果我使用Required<T>,那么它会吞下我的| undefined,但前提是存在?

因此,以下内容非常具有讽刺意味:

export type RequiredDog = Required <{

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

实际上变成了这样:

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

所以bitebark更可选,现在实际上更少可选!
有没有一种方法可以删除?而不同时删除undefined

uqdfh47h

uqdfh47h1#

如果我试图实现一个不剥离undefined属性的Required<>,我可能会这样做:

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;

我在这里对一堆Map类型和条件类型所做的是将值类型 Package 在一个元组中,使其成为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}

好吧,希望能帮上忙;祝你好运!
链接到代码

bxfogqkk

bxfogqkk2#

我仍然喜欢更干净的解决方案,但以下方法似乎有效:
我基本上创建了一个名为'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<Person>就是

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

然后你可以调用Required<T>来得到这个:

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

然后用UnwrapUndefined<T>将其反转,得到

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,因为它似乎将'undefined'吸收到string中。幸运的是,当你运行所有三个步骤时,它最终会得到正确的结果。
gmxoilav

gmxoilav3#

下面是@Simon_Weaver答案的重构。

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

type RequiredDog = Required<Dog>
/* type RequiredDog = {
    bark: "loud" | "quiet" | undefined;
    bite: "nip" | "clamp";
    howl: "short" | "long";
} */

declare const Undefined: unique symbol;
type Undefined = typeof Undefined;
type Mark<T> = undefined extends T ? Exclude<T, undefined> | Undefined : T;
type Unmark<T> = Undefined extends T ? Exclude<T, Undefined> | undefined : T;
type MarkProps<O> = { [P in keyof O]: Mark<O[P]> };
type UnmarkProps<O> = { [P in keyof O]: Unmark<O[P]> };
type RequiredKeepUndefined<O> = UnmarkProps<Required<MarkProps<O>>>;

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

使用唯一的符号类型来标记undefined s可使其免受冲突。
将其分解为更多的助手使其不言自明。
Playground

相关问题