我有一个联合类型,表示用户可以使用表单字段构造的一段数据。基本流程是用户选择他们想做的事情,然后提供正确的UI,并在他们编辑表单字段时更新存储的对象。进行中的对象表示为联合类型的部分化版本,具体取决于用户选择制作的事物的类型。我想用两种方式来引用Partialized类型的类型,但是都有问题。
代码段可以解释更多,但是基本上,回调的签名和类型防护是我想引用进行中值的两种方法。在我提出的两个方案中,都定义了Partialized联合类型,这两个用例之一无法编译。
似乎变体一更正确,更精确,所以令我惊讶的是它未能正确编译。我想尽可能避免转换,以使此代码尽可能地健壮,以便向联合类型添加更多成员。
export interface Key {
type: "key";
key: string;
}
export interface KeyValue {
type: "key-value";
key: string;
value: string;
}
export type Either = Key | KeyValue;
export type Common = Pick<Either, "type" | "key">;
export const Common = {
toString: ({ type, key }: Common): string => null as any,
fromString: (s: string): Common => null as any,
};
// USE CASE 1: This does not work when using variant one, below.
const callback: (v: PartialEither) => void = null as any;
callback(Common.fromString(""));
// USE CASE 2: This does not work when using variant two, below.
// This makes sense, since variant two's definition drops the relationship between `type`
// and the corresponding object shape, so type narrowing can't work.
const either: PartialEither = null as any;
if (either.type === "key-value") {
either.value;
}
// VARIANT ONE
// Comment this out and replace it with variant two to see the errors change, above.
// Using this intermediate type so I can still rely on 'type' as the discriminant property of
// the PartialEither type.
type _PartialEither<T extends Either> = Pick<T, "type" | "key"> & Partial<Omit<T, "type" | "key">>;
export type PartialKey = _PartialEither<Key>;
export type PartialKeyValue = _PartialEither<KeyValue>;
export type PartialEither = PartialKey | PartialKeyValue;
// VARIANT TWO
// Uncomment this out replace variant one with it to see the errors change, above.
// type PartialEither = Pick<Either, "type" | "key"> & Partial<Omit<Either, "type" | "key">>
答案 0 :(得分:1)
对于编译器为什么不理解您的PartialEither
类型,我没有很深入的了解;像这样的差距涉及交集和映射类型(如microsoft/TypeScript#18538)并不是前所未有的……这并不是问题所在。无论如何,我的直觉是,无论是什么问题,它更像是TypeScript中的错误/设计限制,而不太像是您的代码问题。不确定是否存在与此相关的问题,或者您是否想打开它。
不过,如果我想在这里继续,我会尝试将PartialEither
转换为我能想到的最简单的类型……没有交集的对象类型的并集。一种实现方法是使用我称为Expand
的实用程序类型:
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
这会将具体对象类型的交集转换为单个对象类型(因此{a: string} & {b: number}
将变为{a: string; b: number}
),并且也适用于联合。它不会递归到对象属性中。可以编写一个ExpandRecursive
来做到这一点,但是我们不需要它。
然后我们可以这样做:
export type PartialEither = Expand<PartialKey | PartialKeyValue>;
看到现在在IntelliSense中观察到的PartialEither
类型是:
type PartialEither = {
type: "key";
key: string;
} | {
type: "key-value";
key: string;
value?: string | undefined;
}
当您这样做时,错误就会消失。那是我的建议。
回到“这似乎是TypeScript中的错误或设计限制”主题:
请注意,编译器确实认为PartialKey | PartialKeyValue
和Expand<PartialKey | PartialKeyValue>
是可相互分配的类型,否则将出现错误:
type MutuallyAssignable<T extends U, U extends V, V = T> = true;
type Okay = MutuallyAssignable<PartialKey | PartialKeyValue, PartialEither>; // no error
但是在给它们分配类型Common
的值时,编译器对其中一个感到满意,而对另一个感到不满意:
function hmm(common: Common) {
let nope: PartialKey | PartialKeyValue = common; // error
let yep: PartialEither = common; // okay
yep = nope; // okay also ?
}
因此,这里的编译器类型分析中肯定有一些棘手的问题。如果我发现更多信息(例如与此相关的现有问题),我将进行更新;否则...祝你好运!
更新:此可能与microsoft/TypeScript#19927有关,但不确定。我确实看到您的PartialEither
里面有Pick<Key, never>
,但是我不能确定是否是同一问题?