복잡한 매핑 유형 분류

TypeScript에는 mapped type 이라는 유용한 생성 유형 패턴이 있습니다. 첫 번째 원칙에서 시작하여 전체 예제를 구축하여 이해해 봅시다.


에서 님의 비디오를 시청하는 동안 event 에서 GlobalReducer 의 유형을 이해하려고 애쓰면서 꽤 막혔습니다.



이 유형에는 많은 부분이 있으며 적어도 나에게는 그 중 특별히 직관적인 부분이 없습니다. 그것을 이해하려면 먼저 그것을 더 작은 조각으로 분해해야 했습니다.

다음은 해당 프로세스의 연습입니다.

먼저 리듀서 유형을 추가하고 제거할 수 있는 GlobalReducerEvent가 있습니다.

interface GlobalReducerEvent {
    ADD_TODO: {
        text: string
    }
    LOG_IN: {
        email: string
    }
    DELETE_TODO: {
        todo_id: number
    }
}

type ReducerEvent = ... /* what we're about to build */

type GlobalReducer<TState> = (
    state: TState,
    event: ReducerEvent
) => TState


우리는 GlobalReducer가 어떤 이벤트가 포함되어 있는지에 따라 유형 검사를 수행하기를 원합니다. 이를 위해 GlobalReducerEvent를 사용자 영역 코드에서 유용한 유형으로 매핑할 수 있는 몇 가지 재미있는 TypeScript 기능을 사용해야 합니다.

// this is our target type
type ReducerEvent = ({
    type: "ADD_TODO";
    text: string;
}) | ({
    type: "LOG_IN";
    email: string;
}) | ({
    type: "DELETE_TODO";
    todo_id: number;
})

ReducerEvent (event의 유형)까지 빌드하는 것이 여기서 우리의 최종 목표입니다.

keyof 부터 시작하여 모든 이벤트의 합집합 유형을 얻을 수 있습니다.

// What are our different types of events?
type EventUnion = keyof GlobalReducerEvent;
/*
type EventUnion = 'ADD_TODO' | 'LOG_IN' | 'DELETE_TODO'
*/


그것은 최종 결과에서 서로 다른 두 위치에서 사용되기 때문에 퍼즐의 중요한 조각입니다.

다음 몇 단계는 중간 유형의 정렬을 구축하는 것입니다. 원래 유형을 대상 유형에 매핑하는 데 도움이 되는 유형입니다.
in 를 사용하여 EventUnion 의 값에 대한 색인 서명을 만들 수 있습니다(기억은 keyof GlobalReducerEvent ). 지금은 각 키를 any 로 입력합니다.

// Gather up the types of events
// (we'll replace the `any` in a moment)
type EventTypes = {
    [EventType in EventUnion]: any
};
/*
type EventTypes = {
    ADD_TODO: any;
    LOG_IN: any;
    DELETE_TODO: any;
}

same as doing:

type EventTypes = {
    [EventType in keyof GlobalReducerEvent]: any
}
*/


이제 any 로 입력하는 대신 개체에 키를 지정해 보겠습니다. 이 개체는 우리가 매핑하려는 값을 향해 빌드를 시작할 것입니다.

각 이벤트에는 값이 해당 이름과 일치하는 type가 있어야 합니다. EventType는 이벤트 이름에 대한 참조이므로 개체 유형( { type: EventType } )에서 사용할 수 있습니다.

// Event type keyed to itself as an object
// (weird intermediate type, stick with me here)
type EventTypesWithSelf = {
    [EventType in keyof GlobalReducerEvent]: {
        type: EventType
    }
};
/*
type EventTypesWithSelf = {
    ADD_TODO: {
        type: "ADD_TODO";
    };
    LOG_IN: {
        type: "LOG_IN";
    };
    DELETE_TODO: {
        type: "DELETE_TODO";
    };
}
*/


각 이벤트에는 함께 이동해야 하는 데이터를 정의하는 유형이 있습니다. 예를 들어 ADD_TODO에는 수행할 작업을 구성하는 약간의 텍스트가 필요합니다. 따라서 ADD_TODO: { text: string } . 이 다음 부분에서는 각 이벤트의 데이터에 대한 입력을 접을 것입니다.

intersection type literal ( & )을 사용하여 각 이벤트 유형({type: 'ADD_TODO'})과 해당 데이터 유형({text: string})을 결합할 수 있습니다.

// Event type keyed to itself and its data
// (still looks weird, but starting to take shape)
type EventTypesWithSelfAndData = {
    [EventType in keyof GlobalReducerEvent]: {
        type: EventType
    } & GlobalReducerEvent[EventType]
};
/*
type EventTypesWithSelfAndData = {
    ADD_TODO: {
        type: "ADD_TODO";
    } & {
        text: string;
    };
    LOG_IN: {
        type: "LOG_IN";
    } & {
        email: string;
    };
    DELETE_TODO: {
        type: "DELETE_TODO";
    } & {
        todo_id: number;
    };
}

which you can think of as:

type EventTypesWithSelfAndData = {
    ADD_TODO: {
        type: "ADD_TODO";
        text: string;
    };
    LOG_IN: {
        type: "LOG_IN";
        email: string;
    };
    DELETE_TODO: {
        type: "DELETE_TODO";
        todo_id: number;
    };
}
*/


마지막 부분에 도달하기 전에 유형 객체indexed access에 대한 머리를 둘러보겠습니다.

이 작업은 JavaScript 개체에서 키 값에 액세스하는 것과 매우 유사합니다. 이전 단계의 유형을 사용하여 'ADD_TODO' 유형만 가져올 수 있습니다.

// Let's try an indexed access of EventTypesWithSelfAndData
type AddTodoType = EventTypesWithSelfAndData['ADD_TODO']
/*
type AddTodoType = {
    type: "ADD_TODO";
} & {
    text: string;
}
*/


JavaScript 개체 액세스가 작동하는 방식과 다른 점은 개별 값 대신 공용체 유형을 전달하여 입증됩니다.

// What if we try to access multiple types at once with a union
type SomeEventTypes = EventTypesWithSelfAndData['ADD_TODO' | 'DELETE_TODO']
/*
type SomeEventTypes = ({
    type: "ADD_TODO";
} & {
    text: string;
}) | ({
    type: "DELETE_TODO";
} & {
    todo_id: number;
})
*/


결과 형식은 합집합 값에서 인덱싱된 형식으로만 구성된 합집합 형식입니다.

우리는 모든 이벤트 유형의 합집합인 keyof GlobalReducerEvent로 인덱싱된 액세스를 수행하여 이를 한 단계 더 발전시킬 수 있습니다.

// That means we can pass in a union of all our event type names
// to get a union of all the type signatures.
type AllEventTypes = EventTypesWithSelfAndData[keyof GlobalReducerEvent]
/*
type AllEventTypes = ({
    type: "ADD_TODO";
} & {
    text: string;
}) | ({
    type: "LOG_IN";
} & {
    email: string;
}) | ({
    type: "DELETE_TODO";
} & {
    todo_id: number;
})

which, you might remember, is equivalent to this:

type AllEventTypes = ({
    type: "ADD_TODO";
    text: string;
}) | ({
    type: "LOG_IN";
    email: string;
}) | ({
    type: "DELETE_TODO";
    todo_id: number;
})
*/


마지막 예제는 이 전체 ReducerEvent와 동일합니다. 우리는 그것을 모든 구성 요소로 구성했습니다. 재미있는!

전체를 다시 한 번 살펴보겠습니다.

type ReducerEvent = {
    [EventType in keyof GlobalReducerEvent]: {
        type: EventType
    } & GlobalReducerEvent[EventType]
}[keyof GlobalReducerEvent]


바라건대 이것의 모든 다른 부분을 살펴보니 마술처럼 보이기보다는 추론할 수 있는 무언가처럼 보입니다.

이 게시물이 마음에 드셨다면 joining my newsletter을 고려하거나 에서 저를 팔로우하세요. 이것이 도움이 되었거나 질문이 있는 경우 언제든지 저에게 메모를 남겨주세요. 당신의 의견을 듣고 싶습니다!

이것으로 더 자세히 놀고 싶습니까? the TypeScript Playground에서 확인하세요.


GeoJango Maps on Unsplash의 표지 사진

좋은 웹페이지 즐겨찾기