[TS] Type-Challenges 스터디 1주차
![[TS] Type-Challenges 스터디 1주차](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2Fc3cc19f3-5f53-4d3e-97bd-a43222bb8ac6%2Fimage.jpg&w=3840&q=75)
[Easy] 4. Pick
View on GitHub: https://tsch.js.org/4
문제
T에서 K 프로퍼티만 선택해 새로운 오브젝트 타입을 만드는 내장 제네릭 Pick<T, K>을 사용하지 않고 구현하세요.
정답
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
설명
T는 원본 타입K는T의 프로퍼티 키 타입 (extends keyof T를 활용해T의 프로퍼티 키 타입으로 제한)[P in K]는K의 각 프로퍼티 키를 순회하며 새로운 타입을 생성T[P]는P프로퍼티 키(key)의 값(value)을 가져옴
추가 질문
as와extends차이
as: 타입 단언 or 매핑된 타입 변환extends: 타입 제약(generic) or 조건부 타입(conditional type) 정의
// as의 사용(1): 타입 단언
const value: unknown = "hello";
const length = (value as string).length; // 타입 단언
// as의 사용(2): 매핑된 타입 변환
type RenameKeys<T> = {
[K in keyof T as `new_${string & K}`]: T[K];
};
type Original = { id: number; name: string };
type Renamed = RenameKeys<Original>;
// 결과:
// {
// new_id: number;
// new_name: string;
// }
// extends의 사용(1): 타입 제약
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// extends의 사용(2): 조건부 타입 정의
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
[P in K]란 무엇인가?
-
[]는 매핑된 타입에서 특정 키를 순회하며 새로운 타입을 생성할 때 사용(index 아님) -
P는 매핑된 키를 나타내는 변수 -
in은 순회(iteration)를 나타내는 키워드로, K의 각 요소를 순회 -
K는 순회할 키의 집합(K는 일반적으로 keyof T처럼 타입의 키 집합) -
[P in K]는 K에 포함된 키를 하나씩 순회하며 새로운 타입의 키를 정의
type MappedType = {
[P in K]: ValueType; // 매핑된 타입의 키와 값 정의
};
Reference
[Easy] 7. Readonly
View on GitHub: https://tsch.js.org/7
문제
T에서 모든 프로퍼티를 읽기 전용으로 만드는 내장 제네릭 Readonly<T>을 사용하지 않고 구현하세요.
정답
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
설명
[P in keyof T]:T의 모든 프로퍼티 키를 순회하며 새로운 타입을 생성readonly: 프로퍼티를 읽기 전용으로 만듦T[P]는P프로퍼티 키(key)의 값(value)을 가져옴
추가 질문
'readonly' 키워드는 무엇인가?
Typescript고유의Type Modifier- 타입 정의 시 특정 프로퍼티를 읽기 전용으로 선언할 때 사용
- 읽기 전용 프로퍼티는 할당 불가능
- 객체 타입의 프로퍼티, 배열, 매핑된 타입에 적용 가능
- 런타임이 아닌 컴파일 타임에만 동작
// 객체 타입의 프로퍼티
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "Hayou" };
user.id = 2; // 오류: 'id'는 읽기 전용 속성입니다.
// 배열
const numbers: readonly number[] = [1, 2, 3];
numbers[0] = 4; // 오류: 읽기 전용 배열에서는 값을 변경할 수 없습니다.
numbers.push(4); // 오류: 읽기 전용 배열에서는 요소를 추가할 수 없습니다.
// 매핑된 타입
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
id: number;
name: string;
}
type ReadonlyUser = MyReadonly<User>;
const user: ReadonlyUser = { id: 1, name: "Hayou" };
user.id = 2; // 오류: 'id'는 읽기 전용 속성입니다.
Reference
[Easy] 11. Tuple to Object
View on GitHub: https://tsch.js.org/11
문제
배열(튜플)을 받아, 각 원소의 값을 key/value로 갖는 오브젝트 타입을 반환하는 타입을 구현하세요.
정답
type TupleToObject<T extends readonly any[]> = { [P in T[number]]: P };
설명
T는 배열(튜플) 타입T[number]는 배열T의 모든 요소 타입을 나타냄 (배열T의 인덱스에 있는 모든 타입을 집합으로 반환)[P in T[number]]는T[number]의 각 원소를 순회하며 새로운 타입을 생성
추가 질문
[P in T]가 안되는 이유
- 매핑된 타입에서 [P in ...] 구문은 키(key)를 순회하는 역할을 하며, P는 반드시 "키로 사용할 수 있는 값"이어야 함
T는['tesla', 'model 3', 'model X']라는 배열의 "전체"를 가리킴
type T = ["tesla", "model 3", "model X"];
-
매핑된 타입에서 키는 배열 전체가 될 수 없고, 반드시 키로 사용할 수 있는 타입이어야 함
-
반면,
T[number]는 배열T의 요소 타입만을 추출하여 배열의 모든 요소를 하나의 집합으로 표현 -
배열 T의 각 요소가 P로 순회될 수 있도록 만들어 줌
type T = ["tesla", "model 3", "model X"];
type TElement = T[number]; // 'tesla' | 'model 3' | 'model X'
Reference
[Easy] 14. First of Array
View on GitHub: https://tsch.js.org/14
문제
배열(튜플) T를 받아 첫 원소의 타입을 반환하는 제네릭 First<T>를 구현하세요.
정답
type First<T extends any[]> = T extends [] ? never : T[0];
설명
T extends []는 배열T가 비어있는지 확인T[0]는 배열T의 첫 번째 요소를 가져옴T extends [] ? never : T[0]는 배열T가 비어있으면never를 반환하고, 비어있지 않으면 첫 번째 요소를 반환
[Easy] 18. Length of Tuple
View on GitHub: https://tsch.js.org/18
문제
배열(튜플)을 받아 길이를 반환하는 제네릭 Length<T>를 구현하세요.
정답
type Length<T extends readonly any[]> = T["length"];
설명
T extends readonly any[]는 튜플T가 읽기 전용 배열이라는 것을 확인T["length"]는 튜플T의 길이를 반환
추가 질문
왜 읽기 전용 배열이어야 하는가?
- 우선, 예시에서 주어진 튜플은 모두 읽기 전용 배열(as const)이다.
- 그렇다면 as const와 readonly 모두 제거하면 어떻게 될까?
type Length<T extends any[]> = T["length"]; // readonly 제거
const tesla = ["tesla", "model 3", "model X", "model Y"]; // as const 제거
- 이 경우
Expect<Equal<Length<typeof tesla>, 4>>와Expect<Equal<Length<typeof spaceX>, 5>>가 false가 된다. - TypeScript에서 배열 리터럴(
const tesla = ['tesla', 'model 3', 'model X', 'model Y'])은 기본적으로수정 가능한 배열(string[])로 간주됩니다. 즉,typeof tesla의 타입은string[]이다.
type TeslaType = string[]; // 배열의 요소 타입은 string, 길이는 고정되지 않음
type TeslaTypeAsConst = readonly ["tesla", "model 3", "model X", "model Y"]; // 배열의 요소 타입은 string, 길이는 4
Length<T>는 배열 타입에서length프로퍼티를 참조한다. 하지만 배열이 단순히string[]로 추론되면, 배열의 길이에 대한 정보를 포함하지 않음.- 따라서
Length<typeof tesla>는 정확히 4라는 값 타입이 아니라,number타입으로 추론됨
[Easy] 43. Exclude
View on GitHub: https://tsch.js.org/43
문제
T에서 U에 할당할 수 있는 타입을 제외하는 내장 제네릭 Exclude<T, U>를 이를 사용하지 않고 구현하세요.
정답
type MyExclude<T, U> = T extends U ? never : T;
설명
T extends U는T가U에 할당할 수 있는지(T가U의 서브타입인지) 여부를 확인T가U에 할당할 수 있으면never를 반환하고, 그렇지 않으면T를 반환
추가 질문
유니온 타입('a' | 'b' | 'c')에서
T가 동작하는 방식
- 조건부 타입의 분배 법칙(Distributive Property)
T는 유니온 타입의 각 구성 요소에 대해 개별적으로 판단 (이 예시에서는extends를 판단)
Reference
comments
loading…