我目前正试图使一个实用程序类型来展开sniptt monads选项。下面是我到目前为止的代码:
export interface Option<T> {
type: symbol;
isSome(): boolean;
isNone(): boolean;
match<U>(fn: Match<T, U>): U;
map<U>(fn: (val: T) => U): Option<U>;
andThen<U>(fn: (val: T) => Option<U>): Option<U>;
or<U>(optb: Option<U>): Option<T | U>;
and<U>(optb: Option<U>): Option<U>;
unwrapOr(def: T): T;
unwrap(): T | never;
}
export type UnwrappedOptionsType<T> = T extends (infer U)[]
? UnwrappedOptionsType<U>[]
: T extends object
? {
[P in keyof T]: T[P] extends Option<infer R>
? UnwrappedOptionsType<R> | undefined
: UnwrappedOptionsType<T[P]>;
}
: T;
我期望发生的是,类型被推断出来,属性是可选的选项。假设我有以下类型:
type SignUpRequest = {
username: string;
password: string;
email: Option<string>;
}
当我使用UnwrappedOptionsType<SignUpRequest>
时,我希望得到以下类型:
{
username: string;
password: string;
email?: string | undefined;
}
结果是:
{
username: string;
password: string;
email: string;
}
它能够成功地推断出选项的类型,但它从来没有使它也接受undefined
。如何使选项可选?
编辑修改代码,使示例可重现。此外,我特别希望属性是可选的,而不仅仅是可能未定义的。
这里有一个可能的方法:
type UnwrapOptions<T> =
T extends Option<infer U> ? UnwrapOptions<U> | undefined :
T extends readonly any[] ? {[I in keyof T]: UnwrapOptions<T[I]>} :
T extends object ? (
{ [K in keyof T as Option<any> extends T[K] ? never : K]: UnwrapOptions<T[K]> } &
{ [K in keyof T as Option<any> extends T[K] ? K : never]?: UnwrapOptions<T[K]> }
) extends infer U ? { [K in keyof U]: U[K] } : never :
T;
这是一个递归条件类型,因此,必然会有很多边缘情况,它会以令人惊讶或不希望的方式运行。所以在你使用这个或其他东西之前,你应该确保做大量的测试。
好的,让我们检查一下。如果计算UnwrapOptions<T>
,其中T
为…
…对于某些类型
U
的Option<U>
,然后我们递归计算UnwrapOptions<U>
(如果您可能嵌套Option
),并将其与undefined
联合返回。这个undefined
可能只有在Option
是顶级的情况下才需要。也就是说,UnwrapOptions<Option<A>>
应该是UnwrapOptions<A> | undefined
;…数组或元组类型,然后做一个简单的映射数组/元组类型,其中
UnwrapOptions
应用于每个数字元素。即UnwrapOptions<[A, B]>
应为[UnwrapOptions<A>, UnwrapOptions<B>]
;…一个基本类型,然后返回该基本类型。也就是说,
UnwrapOptions<string>
应该是string
,等等;…一个非数组对象类型,那么我们需要将对象拆分为其
Option
可分配属性和其非Option
可分配属性。对于那些不能Option
赋值的属性,我们只做一个直接映射类型,其中UnwrapOptions
应用于每个属性。对于那些是Option
可赋值的,我们做同样的事情,但是使用?
映射修饰符使所有属性都是可选的。然后我们要用一个交点把这两半连接起来。这足以给出你想要的类型,但交集可能很难看;因为像{a: 0} & {b: 1}
这样的对象类型的交集相当于像{a: 0; b: 1}
这样的单个对象类型,所以我使用条件类型推断将交集复制到一个新的类型参数U
中,然后做一个无操作映射类型将交集连接到一个类型中。
好的,让我们测试一下。
type SignUpRequest = {
username: string;
password: string;
email: Option<string>;
}
type UnwrappedSignupRequest = UnwrapOptions<SignUpRequest>;
/* type UnwrappedSignupRequest = {
username: string;
password: string;
email?: string | undefined;
} */
这就是你想要的。如果我们取一个更复杂的类型呢?
interface Foo {
a: string;
b?: number;
c: string[];
d: { z?: string };
e: Option<number>;
f: Option<string>[];
g: Option<string> | number;
h: [1, Option<2>, 3];
i: { y: Option<string> };
j: Option<{ x: Option<{ w: string }> }>;
k: Foo;
}
,
type UnwrappedFoo = UnwrapOptions<Foo>;
/* type UnwrappedFoo = {
a: string;
b?: number | undefined;
c: string[];
d: {
z?: string | undefined;
};
f: (string | undefined)[];
h: [1, 2 | undefined, 3];
i: {
y?: string | undefined;
};
k: any; // <-- this displays as any but it is not
e?: number | undefined;
g?: string | number | undefined;
j?: {
x?: {
w: string;
} | undefined;
} | undefined;
} */
我认为这是合理的。唯一令人困惑的是,IntelliSense将递归部分(k
属性)显示为any
。但它确实会跟踪那个类型,你可以在检查属性时看到:
declare const uFoo: UnwrappedFoo;
uFoo.k;
/* (property) k: {
a: string;
b?: number | undefined;
c: string[];
d: {
z?: string | undefined;
};
f: (string | undefined)[];
h: [1, 2 | undefined, 3];
i: {
y?: string | undefined;
};
k: any;
e?: number | undefined;
g?: string | number | undefined;
j?: {
x?: {
w: string;
} | undefined;
} | undefined;
} */
就是这样,我觉得看起来不错。但是你应该测试它并相应地更新。
Playground链接到代码
您需要为UnwrappedOptionsType类型添加一个额外的类型保护以使该选项成为可选的:
type UnwrappedOptionsType<T> = T extends (infer U)[]
? UnwrappedOptionsType<U>[]
: T extends object
? {
[P in keyof T]: T[P] extends Option<infer R>
? (UnwrappedOptionsType<R> | undefined) | undefined
: UnwrappedOptionsType<T[P]>;
}
: T;
添加了类型保护后,SignUpRequest类型的UnwrappedOptionsType将为:
type UnwrappedOptionsType<SignUpRequest> = {
username: string;
password: string;
email?: (string | undefined) | undefined;
}
输出如下所示:
{
username: string;
password: string;
email?: string | undefined;
}
这就是你想要的吗?