“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

Most TypeScript developers use TypeScript at a rudimentary level.

Don’t believe it? Try the TypeScript Weekly Challenge!

2 challenge topic is: how to Pick < A | B > remained after the Union Type field in the relationship. Let’s review the title first

Subject to review

For type definitions:

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.// ...
}; // There may be more

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

After TypeScript conversion, PickData becomes :(not expected)

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

The actual expected PickData result should be:

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

Redefine the PickData in the question so that it retains the mutually exclusive matching relationship in the original Data even after picking the Type and value fields.

Try to do this with minimal code volume and type redundancy.

The answers posted

type PickUnion<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
type PickData = PickUnion<Data, 'type' | 'value'>;
Copy the code

Magic? Plus T extends any? XXX: Never! Why is that?

The principle of analytic

Pick

is actually a TypeScript Mapped Type feature:
,>

Pick<T, U extends keyof T> = {
    [K in U]: T[K]
}
Copy the code

Mapped Type literally means Mapped Type. A map is a mapping from one Type to another. So for the title, Pick < Data, ‘type’ | ‘value’ > the mapping result:

{
    type: Data['type'].value: Data['value']}Copy the code

As you can see, there is no correlation between the fields of the mapped Type, so the relationship in the Union Type does not exist.

As a result, TypeScript provides a mechanism called Distributive Conditional Types to solve similar problems. It is written as:

type XXX<T> = T extends any ? AAA : BBB;
Copy the code

If A generic Type parameter T is A condition, such as the Union Type), the TypeScript when Type deduction restructuring operation, for example, suppose you give T transfer is A | B:

/ / before the restructuring
type XXX<A | B> = (A | B) extends any ? AAA : BBB;
// After reorganization ↓↓↓
type XXX<A | B> = (A extends any ? AAA : BBB) | 
    (B extends any ? AAA : BBB);
Copy the code

So when we implement PickUnion:

type PickUnion<T, K extends keyof T> = T extends any ? Pick<T, K> : never;
Copy the code

We passed in T: Data because it is A condition type A | B | C, so it is restructuring into:

(A extends any ? Pick<A, K> : never) |
(B extends any ? Pick<B, K> : never) |
(C extends any ? Pick<C, K> : never)
Copy the code

In this way, the field relationships in the condition type are preserved.

For more information on Conditional type regrouping, see the TypeScript official manual document Distributive Conditional Types


Review past challenges

  • Issue 1: Constraint type relationships between multiple function parameters

If you’re interested in full-stack development with TypeScript, consider TSRPC, the only open source TypeScript RPC framework in the world that supports runtime automatic detection and binary serialization of TypeScript complex types.

GitHub:github.com/k8w/tsrpc Chinese document: TSRPC. Cn video tutorial: www.bilibili.com/video/BV1hM…