TypeScript

[TS 타입 단언(Type Assertion) as] 타입을 지정하고 싶을 경우

개발조각 2023. 3. 8. 16:54
728x90
반응형

마음일기 팀프로젝트를 하면서 타입이 꼬이는 현상이 발생했습니다.

(사실 타입은 아무 잘못이 없고 내가 잘못한 거지만;;;)

 

 

문제 발생


작업하는 걸 간단하게 설명을 하자면 

지금 마음일기라는 프로젝트를 진행하고 있습니다.

일기를 쓰면 해당일기가 무슨 감정인지에 따라 마음이 결정되는 그런 프로젝트인데요.

여기서 해당 달의 가장 많이 차지한 마음을 추출하는 기능을 구현을 하고 있었습니다.

 
유저가 쓴 일기를 달 단위로 조회하는 API를 가지고와서 EmotionData에 담아주고
const { data, isSuccess } = useQuery({
    queryKey: [MONTH_DIARY.LIST, { year: dayJs.year(), month: dayJs.month() + 1 }],
    queryFn: () => fetchMonthDiaryList({ year: dayJs.year(), month: dayJs.month() + 1 }),
    placeholderData: {},
});
if (!isSuccess) return null;

const EmotionData: Record<Emotion, number> = {
    confidence: 0,
    excitement: 0,
    thanks: 0,
    comfort: 0,
    worry: 0,
    sad: 0,
    hurt: 0,
    angry: 0,
};

Object.values(data).forEach((diary) => (EmotionData[diary.emotion] += 1));

데이터를 담은 EmotionData를 통해 해당 달에 가장 많이 차지하는 마음을 골라주는 기능을 구현해 보았습니다.

export const max = (EmotionData: Record<Emotion, number>) => {
    const max = Math.max(...Object.values(EmotionData));
    const maxEmotion: string[] = Object.entries(EmotionData)
        .filter((emotion) => emotion[1] === max)
        .map((list) => {
            return emotionIcon[list[0]].name;
        });

    return maxEmotion.length < 4 ? maxEmotion.join(" ") : "";
};

그러나 이렇게 작성을 하면 빨간 줄을 마주하게 되었습니다...

이렇게 말이죠.

 

저 빨간 줄의 에러문구를 파파고에 돌리면

형식 'string' 식을 사용하여 'Record<Emotion, {name: string; 아이콘: string; }>'을(를) 인덱싱할 수 없으므로 요소에 암시적으로 'any' 형식이 있습니다.
'Record<Emotion, {name: string; 아이콘: string; }>' 형식에서 매개 변수가 'string'인 인덱스 서명을 찾을 수 없습니다.

이런 식으로 나옵니다.

 

 

해결방안


일단 무슨 이유인지부터 파악을 해보았습니다.

 

emotionIcon에는 이전에 해당 마음(영어)을 해당 마음(한글), icon을 한 번에 묶어두었습니다.

한글이름이랑 아이콘은 어디 가서도 자주 사용될 테니까 미리 묶어 두었고요.

export const emotionIcon: Record<Emotion, { name: string; icon: string }> = {
    confidence: { name: "자신감", icon: confidence },
    excitement: { name: "신남", icon: excitement },
    thanks: { name: "감사", icon: thanks },
    comfort: { name: "편안", icon: comfort },
    worry: { name: "불안", icon: worry },
    sad: { name: "슬픔", icon: sad },
    hurt: { name: "상처", icon: hurt },
    angry: { name: "분노", icon: angry },
};

그리고 이 프로젝트에서는 8개의 감정으로만 분석하기로 해서 8가지 마음을 타입으로 지정해 버렸습니다.

export type Emotion =
    | "confidence"
    | "excitement"
    | "thanks"
    | "comfort"
    | "worry"
    | "sad"
    | "hurt"
    | "angry";

 

 

이렇게 쭉 보니 알겠더라고요.

emotionIcon[list[0]].name;

내 생각

list [0]은 8가지 감정이니까 당연히 되겠지

 

타입스크립트 식 생각

emotionIcon안에는 Emotin타입(8가지 감정)밖에 못 들어가는데???

list[0]이 Emotion타입이 아닌 다른 string일 수도 있으니까 안돼!!!

 

결론 : list[0]이 Emotion 타입이 아닐 수 있어서

 

 

이걸 어떻게 해결해야 되나 고민하고 있었는데 타입단언 as가 생각나더라고요.

const emotionName = list[0] as Emotion;
return emotionIcon[emotionName].name;

이렇게 써주었더니 말끔하게 해결되었습니다.

 

export const max = (EmotionData: Record<Emotion, number>) => {
    const max = Math.max(...Object.values(EmotionData));
    const maxEmotion: string[] = Object.entries(EmotionData)
        .filter((emotion) => emotion[1] === max)
        .map((list) => {
            const emotionName = list[0] as Emotion;
            return emotionIcon[emotionName].name;
        });

    return maxEmotion.length < 4 ? maxEmotion.join(" ") : "";
};

 

 

타입 단언(Type Assertion) as란 무엇인가


 

as를 보면 바로 생각이 드는 건 JavaScript의 import에서 쓰는 as가 생각났습니다.

import * as name from "module-name";
import { export1 as alias1 } from "module-name";

이런 식으로 module-name에 있는 export 한 값의 이름을 지정해서 가져올 수 있습니다.

개인적인 견해지만 타입스크립트에서도 as가 타입을 지정해주고 싶을 때 쓰는 거라 import의 as와 유사하다는 생각이 들었습니다.

 

 

 

그래도 as에 대해 자세히 알기 위해 타입스크립트 문서를 살펴보자면

https://joshua1988.github.io/ts/guide/type-assertion.html#%ED%83%80%EC%9E%85-%EB%8B%A8%EC%96%B8-type-assertion

 

타입 단언 | 타입스크립트 핸드북

타입 단언(Type Assertion) 타입 단정은 개발자가 해당 타입에 대해 확신이 있을 때 사용하는 타입 지정 방식입니다. 다른 언어의 타입 캐스팅과 비슷한 개념이며 타입스크립트에서 특별히 타입을

joshua1988.github.io

타입 단언(Type Assertion) 
타입 단정은 개발자가 해당 타입에 대해 확신이 있을 때 사용하는 타입 지정 방식입니다. 다른 언어의 타입 캐스팅과 비슷한 개념이며 타입스크립트에서 특별히 타입을 체크하지 않고, 데이터의 구조도 신경 쓰지 않습니다.

 

그래서 저 같은 경우도 list[0]이 Emotion타입이라는 확신이 있었기 때문에 사용했습니다.

 

 

이것만 살펴보기에 설명이 부족한 감이 있어서 더 찾아보았습니다.

https://www.typescriptlang.org/docs/handbook/2/everyday-types.html

 

Documentation - Everyday Types

The language primitives.

www.typescriptlang.org

여기서 Type Assertion부분을 읽어보면

Type Assertions
때때로 당신은 TypeScript가 알 수 없는 값의 유형에 대한 정보를 갖게 될 것이다.

예를 들어, document.getElementById를 사용하는 경우 TypeScript는 이것이 어떤 종류의 HTMLElement를 반환한다는 것만 알고 있지만 페이지에는 항상 지정된 ID를 가진 HTMLCanvasElement가 있다는 것을 알고 있을 수 있습니다.

이 경우 Tyoe Assertions을 사용하여 보다 구체적인 유형을 지정할 수 있습니다:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

 

쉽게 설명하자면

타입스크립트에서는 document.getElementById를 사용하면 HTMLElement를 사용한다는 걸 알고 있지만
HTMLElement중에 HTMLCanvasElement만 사용하는 건지는 잘 몰라요.
하지만 개발자는 myCanvas는 당연히 HTMLCanvasElement만 들어가는 걸 아는 상황이고요.
그래서 as HTMLCanvasElement를 적어주면 타입이 좁혀지니 에러의 확률을 줄일 수 있습니다.
구체적이고 자세한 타입을 지정하고 싶을 경우에 사용하면 됩니다.

 

결론


as 키워드란 타입스크립트가 감지하지 못하는 애매한 타입 요소들을 직접 명시해 줄 때 사용하면 되는 키워드입니다.

728x90
반응형