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