TypeScript 每周挑战第 2 期:如何在 Pick 之后仍然保持 Union 关系?

1,062 阅读2分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

其实大多数 TypeScript 开发者,对 TypeScript 的利用,还停留在初级水平。

不信?来试试 TypeScript 每周挑战吧!

第 2 期挑战题目全新开启,答案将在 2021-07-08(周五)下午 17:00 公布

背景

TypeScript 中有很多使用的工具类型,其中包括常用的 Pick<T>,它可以用于从一个类型中提取指定的字段,例如:

interface Type1 {
    a: string,
    b: boolean,
    c: number
}

// { a: string, b: boolean }
type Type2 = Pick<Type1, 'a' | 'b'>;

问题

但如果你 Pick 的是一个 Union Type,则可能出现一些问题,例如下面的 PickData

type Data = {
    type: 'a',
    value: {
        a: string
    },
    a1: string,
    a2: string,
    // ...
} | {
    type: 'b',
    value: {
        b: string
    },
    b1: string,
    b2: string,
    // ...
} | {
    type: 'c',
    value: {
        c: string
    },
    c1: string,
    c2: string,
    // ...
}; // 可能还有更多项

type PickData = Pick<Data, 'type' | 'value'>;

如你所见,Data 是一个 Union Type,根据其 type 字段值的不同,value 字段呈现出不同的格式。PickData 提取了 typevalue 字段,显然我们希望这 2 个字段仍然保持它们在 Data 中的匹配关系。

即期望 PickData 等同于:

type PickData = {
    type: 'a',
    value: {
        a: string
    }
} | {
    type: 'b',
    value: {
        b: string
    }
    // ...
} | {
    type: 'c',
    value: {
        c: string
    }
};

但实际上,经过 TypeScript 的转换处理,PickData 变成了:(不期望)

type PickData = {
    type: "a" | "b" | "c";
    value: {
        a: string;
    } | {
        b: string;
    } | {
        c: string;
    };
}

即 TypeScript 解析后的 PickData 失去了 Union Type 中的字段匹配关系。

相应的,这段代码会发生报错:(不期望发生)

function test(data: PickData) {
    if (data.type === 'a') {
        // ERROR:Property 'a' does not exist on type '{ a: string; } | { b: string; } | { c: string; }'.
        console.log(data.value.a);
    }
}

题目需求

重新定义 问题 中的 PickData,使其在 Picktypevalue 字段后仍能保持原始 Data 中的互斥匹配关系。

请尝试在代码量和类型冗余最小的情况下实现。

欢迎你将答案和思路评论在下方评论区,正确答案将在 2021-07-08(周五)下午 17:00 公布。

(正文完)

往期挑战回顾


如果你对使用 TypeScript 进行全栈开发感兴趣,欢迎关注目前世界上唯一支持 TypeScript 复杂类型运行时自动检测和二进制序列化的TypeScript 开源 RPC 框架 —— TSRPC

GitHub:github.com/k8w/tsrpc
中文文档:tsrpc.cn
视频教程:www.bilibili.com/video/BV1hM…