【问题标题】:Union of partial types in Typescript can't be type-narrowedTypescript 中部分类型的联合不能进行类型缩小
【发布时间】:2019-09-04 09:16:12
【问题描述】:

我有一个联合类型,它表示用户可以使用表单字段构造的一段数据。基本流程是用户选择他们想要制作的东西,然后我呈现正确的 UI,当他们编辑表单字段时,我更新存储的对象。进行中的对象表示为联合类型的部分化版本,具体取决于用户选择制作的事物的类型。有两种方式我想引用 Partial-ized 类型,但都有问题。

代码 sn-p 可以解释更多,但基本上,回调的签名和类型保护是我想引用进行中值的两种方式。在我想出的定义部分化联合类型的两种方案中,两个用例之一无法编译。

看起来变体一更正确和精确,所以我很惊讶它无法正确编译。我想尽可能避免强制转换,以使这段代码尽可能健壮,以便向联合类型添加更多成员。

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">>

(playground link)

【问题讨论】:

    标签: typescript union-types


    【解决方案1】:

    我对为什么编译器不理解您的 PartialEither 类型没有深入了解;像microsoft/TypeScript#18538 这样的涉及交叉点和映射类型的差距并不是史无前例的......并不是说这正是那个问题。无论如何,我的直觉是,无论问题更像是 TypeScript 中的错误/设计限制,而不是代码问题。不确定是否存在涵盖此问题的现有问题,或者您是否想打开一个。


    不过,如果我想在这里继续,我会尝试将PartialEither 变成我能做到的最简单的类型……没有交集的对象类型的联合。一种方法是使用我称为 Expand 的实用程序类型:

    type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
    

    这会将具体对象类型的交集转换为单个对象类型(因此{a: string} &amp; {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 | PartialKeyValueExpand&lt;PartialKey | PartialKeyValue&gt; 是可相互赋值的类型,否则会出现以下错误:

    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&lt;Key, never&gt;,但我不知道这是否是同一个问题?

    Link to code

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-22
    • 2021-02-26
    • 2021-06-09
    • 2021-04-29
    • 1970-01-01
    • 2022-10-02
    • 1970-01-01
    • 2021-10-21
    相关资源
    最近更新 更多