개발자

typescript 에서 동일한 string 타입에 들어가는 데이터를 다르게 type check 하는 방법이 있나요 ?

2023년 05월 19일조회 142

typescript 고수분들께 질문이 있습니다! 서버에서 string 값을 내려주는데, 프론트에서 해당 string 을 파싱해서 만든 string 을 이용해야 해요 근데 둘다 type 을 string 으로 두게 되면 서로 동일한 타입이 되기 때문에, 파싱 로직을 거치지 않고 할당해도 타입스크립트가 경고하지 않아요 그래서 ""파싱 전의 문자열"" 과 ""파싱 후의 문자열"" 둘을 서로 호환되지 않는 타입으로 만들고 싶은데, 혹시 좋은 방법이 있을까요? BeforeParse 를 string 대신 unknown 으로 주면 원하는 걸 만들 수 있긴 한데, 그냥 좀더 좋은 방법이 없을까 고민중입니다.

이 질문이 도움이 되었나요?
'추천해요' 버튼을 누르면 좋은 질문이 더 많은 사람에게 노출될 수 있어요. '보충이 필요해요' 버튼을 누르면 질문자에게 질문 내용 보충을 요청하는 알림이 가요.
profile picture
익명님의 질문

답변 1

인기 답변

이승로님의 프로필 사진

질문에 정확한 예시가 없어서 정확한 답변을 드리기는 어렵지만, 조건부 타입과 infer 키워드, 또한 경우에 따라 재귀적 타입 선언을 사용하시면 될 것 같습니다. 조건부 타입이란, T extends U ? X : Y 와 같이 3항 연산자의 형태를 띄는 문법으로, T가 U 타입으로 추론될 수 있는 경우 X를 반환하고, 그렇지 아니한 경우 Y를 반환하는 문법입니다. 예를 들어, type A = "hello world" extends string ? true : false; 에서 A의 타입은 true가 됩니다. infer 키워드는 위에서 언급한 조건부 타입 내에서 활용될 수 있는 키워드로, extends 좌항 구성요소의 타입을 추론할 수 있는 키워드입니다. 예를 들어, type B = "hello world" extends `hello ${infer R}` ? R : never; 에서 B의 타입은 "world"가 됩니다. 또 다른 예시로 type C<T> = T extends Promise<infer R> ? R : never; 일 때, C<Promise<string>>의 타입은 string이 됩니다. 재귀적 타입 선언이란, 말 그대로 타입을 재귀적으로 선언하여 결과 타입을 점진적으로 발전시켜 반환하는 타입 선언 방식을 뜻합니다. 예를 들어, Split<"1 2 3">이 ["1", "2", "3"]으로 추론되도록 Split 타입을 구현하기 위해서는 type Split< T extends string, D extends string = " ", R extends string[] = [] > = T extends `${infer A}${D}${infer B}` ? A extends '' ? Split<B, D, R> : Split<B, D, [...R, A]> : T extends '' ? R : [...R, T]; 위와 같이 Split 타입을 작성할 수 있습니다. 해석하자면, 먼저 입력값에 해당하는 T, 구분자에 해당하는 D, 결과값에 해당하는 R을 각각 제네릭으로 입력받습니다. 이 때 D와 R에는 각각 " ", []의 기본값을 설정합니다. 이후 이어지는 첫번째 조건문에서는 T를 구분자를 기준으로 A, B 두 부분으로 나뉘는 지 확인합니다. 만약 이 조건문이 참인 경우, B에 대해 Split 타입을 다시 선언합니다. 다만, A가 비어있는지를 확인하여 빈 경우에는 타겟만 B로 설정하여 그대로 Split 타입을 선언하고, 그렇지 아니한 경우에는 R에 A를 추가하여 Split 타입을 선언합니다. 다시 첫번째 조건문으로 돌아와서, T가 구분자를 기준으로 A, B 두 부분으로 나뉘지 않는 경우에는 T가 빈 값인지를 확인합니다. 만약 T가 빈 값이면 R을 리턴하고, 그렇지 아니한 경우 R에 T를 추가하여 리턴합니다. 설명이 조금 복잡하죠..? Split의 리턴값을 쉽게 표현하자면 아래와 같습니다. 타겟이 "앞 뒤" 형태이고 앞이 빈 값인가? -> Split<뒤, " ", 앞> 타겟이 "앞 뒤" 형태이고 앞이 빈 값이 아닌가? -> Split<뒤, " ", [...R, 앞]> 타겟이 "앞 뒤" 형태가 아니고 타겟이 빈 값인가? -> R 타겟이 "앞 뒤" 형태가 아니고 타겟이 빈 값이 아닌가? -> [...R, 타겟] Split<"a b c">가 동작하는 과정을 살펴보시면 아마 좀 더 이해가 수월하지 않을까 싶습니다. 1. "a b c"는 구분자인 " "를 기준으로 "a", "b c" 두 부분으로 나뉘어지고, "a"는 빈 값이 아니기 때문에 Split<"b c", " ", ["a"]> 타입을 반환합니다. 2. "b c"는 구분자인 " "를 기준으로 "b", " c" 두 부분으로 나뉘어지고, "b"는 빈 값이 아니기 때문에 Split<" c", " ", ["a", "b"]> 타입을 반환합니다. 3. " c"는 구분자인 " "를 기준으로 "", "c"로 나뉘어지고, ""는 빈 값이기 때문에 Split<"c", " ", ["a", "b"]> 타입을 반환합니다. 4. "c"는 구분자인 " "를 기준으로 나뉘어지지 않고, "c"는 빈 값이 아니기 때문에 ["a", "b", "c"] 타입을 반환합니다. 설명이 좀 길었는데, 도움이 되셨을 지 모르겠네요. 마지막 예시로 JSON 타입에서 각 value들을 유니온 타입으로 뽑아내는 타입 선언을 보여드릴게요. 도움이 되었으면 좋겠습니다!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const response = '{ "name": "world", "email": "example@sample.co.kr" }'

type ExtractValue<Entry extends any> = Entry extends `${infer Key}: "${infer Value}"` ? Value : never;

type UnionOfValues<
    Entries extends any[],
    Result extends string = never
> = Entries extends [infer Entry, ...infer Rest] ? UnionOfValues<Rest, Result | ExtractValue<Entry>> : Result;

type SplitString<
    Target extends string,
    Divider extends string = ", ",
    Result extends string[] = []
> = Target extends `${infer A}${Divider}${infer B}`
    ? A extends '' ? Result : SplitString<B, Divider, [...Result, A]>
    : (Target extends '' ? Result : [...Result, Target]);

type JsonToEntries<
    Json extends string
> = SplitString<(Json extends `{ ${infer Content} }` ? Content : never)>;

type OneOfParsedValue<Json extends string> = UnionOfValues<JsonToEntries<Json>>;

type ValueOfResponse = OneOfParsedValue<typeof response>;

// See on Playground: https://bit.ly/extract-json-values-ts

지금 가입하면 모든 질문의 답변을 볼 수 있어요!

현직자들의 명쾌한 답변을 얻을 수 있어요.

또는

이미 회원이신가요?

목록으로
키워드로 질문 모아보기

실무, 커리어 고민이 있다면

새로운 질문 올리기

지금 가입하면 모든 질문의 답변을 볼 수 있어요!