
안녕하세요! 😊
타입스크립트를 공부하면서 헷갈렸던 부분들을 직접 정리해보았습니다.
개인적인 학습 기록이지만, 누군가에게 도움이 되셨으면 좋겠습니다! 🙇🏻♂️
이번 포스팅은 [TS] 다시 정리하는 TypeScript - 1에 이어 작성한 글입니다.
현재 실무에서 TypeScript를 사용한 지 어느덧 2년을 바라보고 있습니다. 다양한 프로젝트를 경험하면서 동료 개발자들과의 협업도 많아졌고, 자연스럽게 '타입을 더 명확하고 유연하게 설계하는 방법은 없을까?'라는 고민이 생기기 시작했습니다.
간단하게 예를 들어, 아래와 같은 구조를 만들었던 적이 있습니다.
type Person = {
name: string;
age: number;
gender: "MALE" | "FEMALE";
address: string;
job: "developer" | "student" | "designer";
};
type PersonSummary = {
name: string;
age: number;
address: string;
};
당시에는 구조가 조금만 달라도 별도로 타입을 만들곤 했는데, 나중에 Omit 유틸리티 타입을 활용하면 더 간결하게 표현할 수 있다는 것을 알게 되었습니다.
type PersonSummary = Omit<Person, "gender" | "job">;
이처럼 타입스크립트에는 실무에서 더 유연하게 활용할 수 있는 기능들이 많다는 걸 새삼 느끼게 되었고, 이를 체계적으로 공부해보고 싶다는 생각이 들었습니다.
마침 한 입 챌린지 6기가 열리게 되어, 타입스크립트를 주제로 참여하게 되었습니다!
혼자 공부하면 느려질 때도 있고 지루해질 수 있는데, 이번 기회를 통해 집중력 있게 학습하고자 합니다.
이번 포스팅은 Day 8부터 Day 14까지, 챌린지 수업과 퀴즈를 따라가며 배운 내용과 인사이트를 정리해보려고 합니다!
8일차는 함수 오버로딩, 사용자 정의 타입 가드, 인터페이스 및 확장 방식을 중심으로 TypeScript의 고급 타입 시스템을 정리했습니다.
하나의 함수를 매개변수의 개수나 타입에 따라 여러 버전으로 정의하는 방식
하나의 함수 func 모든 매개변수의 타입 number
// 오버로드 시그니처
function func(a: number): void;
function func(a: number, b: number, c: number): void;
// 구현 시그니처(실제 구현부)
function func(...args: number[]): void {
if (args.length === 1) {
console.log(args[0] * 20);
} else if (args.length === 3) {
console.log(args[0] + args[1] + args[2]);
}
}
func(1); // ✅ 20 출력
func(1, 2, 3); // ✅ 6 출력
func(); // ❌ 매개변수 없음
func(1, 2); // ❌ 시그니처 없음
⚠ 오버로드를 사용할 땐 정의한 오버로드 시그니처 중 하나와 반드시 일치해야 합니다. 실제 구현 시그니처는 가장 마지막에 하나만 존재합니다.
오버로드 시그니처 func를 정의해줌으로써 앞으로 func는 이렇게 정의하겠다 라는 식으로 정의한 것
만일, 다음과 같이 구현부를 정의하게 된다면
function func(a: number, b:number, c:number){}
아래의 오버로드 시그니처의 의미가 없어집니다.
function func(a: number): void;
type Dog = {
name: string;
isBark: boolean;
};
type Cat = {
name: string;
isScratch: boolean;
};
type Animal = Dog | Cat;
// 사용자 정의 타입 가드
function isDog(animal: Animal): animal is Dog {
return (animal as Dog).isBark !== undefined;
}
// 반환값이 true이면 매개변수로 받은 또는 전달한 animal이 Dog 타입이구나 라고 명시한다.
function warning(animal: Animal) {
if ("isBark" in animal) {
// 🐶 강아지
} else if ("isScratch" in animal) {
// 🐱 고양이
}
}
animal is Dog는 타입스크립트에게 조건이 true일 경우 animal이 Dog 타입임을 단언하는 역할을 합니다.
타입에 이름을 지어주는 또 다른 문법
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "김범수",
age: 27,
};
❓ 인터페이스에서 함수 오버로딩은 어떻게?
함수 타입 표현식 ❌ - 이렇게 쓰면 오버로드 인식 안됨
interface Person {
sayHi: () => void;
sayHi: (a: number, b: number) => void;
}
✔ 호출 시그니처 방식 ✅
interface Person {
sayHi(): void;
sayHi(a: number, b: number): void;
}
interface Person {
name: string;
age: number;
sayHi(): void;
sayHi(a: number, b: number): void;
}
person.sayHi();
person.sayHi(1,2);
메서드의 오버로딩을 사용하고 싶다면 다음과 같이 호출 시그니처를 써야함
sayHi(): void;
sayHi(a: number, b: number): void;
| 항목 | type | interface |
|---|---|---|
| 유니온( | )/인터섹션(&) 가능 | ✅ |
| 선언 병합 | ❌ | ✅ |
| 확장 가능성 | 제한적 | 유연함 |
| 객체 구조 정의 | 가능 | 특화됨 |
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
isBark: boolean;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface Chicken extends Animal {
isFly: boolean;
}
interface DogCat extends Dog, Cat {}
const hybrid: DogCat = {
name: "하이브리드",
age: 3,
isBark: true,
isScratch: false,
};
동일한 이름의 인터페이스를 여러 번 선언하면 자동으로 병합
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: "김범수",
age: 27,
};
이미 존재하는 인터페이스를 추가적으로 확장할 때 사용
interface Lib {
a: number;
b: number;
}
interface Lib {
c: string;
}
const lib: Lib = {
a: 1,
b: 2,
c: "추가됨",
};
interface IPerson {
name: string;
}
type Type1 = number | string;
타입과 다르게 interface는 유니온이나 intersection을 만들 수 없다.
그래서, 사용할 거면 타입에다가 별칭을 사용하거나 그런식으로 사용을 해야한다.
interface의 이름을 정의할 때 앞에다가 'I'Person 과 같은 I를 쓰는 관습들이 있다. 하지만 논란이 있다.
헝가리안 표기법이라고 하는데 js 프로그래밍기법을 잘 안쓴다.
'_' 이거나 파스칼, 카멜 스네이크를 쓰는데 헝가리안까지 써야하나? 라는 이야기가 있다.
Day 9는 클래스와 상속, 접근 제어자, 인터페이스 구현 등 객체지향 개념을 TypeScript 문법과 함께 정리한 날입니다.
클래스란 객체를 만들어내는 틀
붕어빵이 객체라면, 붕어빵 기계가 클래스
let studentA = {
name: "김범수",
grade: "A+",
age: 27,
study() {
console.log("열심히 공부");
},
introduce() {
console.log("헬로");
},
};
class Student {
name;
grade;
age;
constructor(name, grade, age) {
this.name = name;
this.grade = grade;
this.age = age;
}
study() {
console.log(`${this.name} 공부 중...`);
}
introduce() {
console.log("안녕~");
}
}
const studentB = new Student("김범준", "B", 27);
studentB.study(); // 김범준 공부 중...
studentB.introduce(); // 안녕~
class StudentDeveloper extends Student {
faboriteSkill;
// 생성자 - 실제로 객체를 생성을 하는 메서드이다.
constructor(name, garde, age, favoriteSkill){
// 부모클래스의 생성자를 호출합니다.
super(name, grade, age)
this.favoriteSkill = favoriteSkill
}
const studentDeveloper = new StudentDeveloper("김범수", "A", 28, "Javascript")
}
타입스크립트 클래스는 자바스크립트의 클래스 + 타입 시스템을 결합한 형태입니다.
const employee = {
name:"김범수",
age:27,
position:"developer",
work(){
console.log("일함")
}
}
class Employee {
name: string;
age: number;
position: string;
constructor(name: string, age: number, position: string) {
this.name = name;
this.age = age;
this.position = position;
}
work() {
console.log("열심히 일함");
}
}
class ExecutiveOfficer extends Employee {
// 필드
officeNumber: number;
// 생성자
// 파생 클래스의 생성자는 super 호출을 포함해야 한다.
constructor(name:string, age:number, position:string; officeNumber:number){
super(name,age,position);
this.offceNumber = officeNumber;
}
}
const employeeB = new Employee("김범수",27,"개발자");
// 타입스크립트의 클래스는 타입으로도 활용할 수 있다.
const employeeC :Employee = {
name:"",
age:0,
position:"",
work(){
}
}
특정 필드나 메서드의 접근할 수 있는 범위를 설정하는 문법
=> public private protected
private를 사용하면, 외부에서, 파생클래스에서도 this.name으로 접근 안된다.
만약 파생클래스에서라도 접근 허용하려면 protected(Public private 중간인 느낌) 접근제어자를 사용하자.
class User {
public name: string;
private password: string;
protected email: string;
constructor(name: string, password: string, email: string) {
this.name = name;
this.password = password;
this.email = email;
}
}
class PremiumUser extends User {
showEmail() {
console.log(this.email); // ✅ 가능
// console.log(this.password); // ❌ private이므로 접근 불가
}
}
| 키워드 | 설명 |
|---|---|
public | 어디서든 접근 가능 (기본값) |
private | 클래스 내부에서만 접근 가능 |
protected | 클래스 내부 + 자식 클래스에서만 접근 가능 |
클래스가 인터페이스를 구현(implements) 하면, 해당 인터페이스가 요구하는 구조를 따라야 합니다.
interface CharacterInterface {
name:string;
moveSpeed:number;
move(): void;
}
// CharacterInterface는 설계도라고 하여 캐릭터 class는 해당 설계도를 구현한다는 의미
class Character implements CharacterInterface{
name:string;
moveSpeed:number
constructor(name:string, moveSpeed:number){
this.name=name
this.moveSpeed = moveSpeed
}
move(): void{
consolel.log(`${this.moveSpeed} 속도로 이동`)
}
// interface로 정의하는 필드들은 무조건 public만 정의할 수 있기때문.
}
제네릭(Generic)의 기본 개념부터 실전 응용까지 정리한 날입니다.
일반적인 포괄적인 함수 - 모든 타입에 두루두루 사용할 수 있는 범용적인 함수나 타입을 만드는 방법
function func<T>(value: T): T {
return value;
}
<T>는 타입 변수(Type Variable) 로, 실제 타입은 함수를 호출할 때 결정됨
타입 변수와 함께 타입의 값을 인수로 받아서 범용적으로
let a = func<number>(10); // T = number
let b = func<string>("hi"); // T = string
function swap(a: any, b: any){
return [b, a];
}
const [a, b] = swap(1, 2);
=> swap("1", 2); 하면 타입 오류 발생.
a와 b의 타입이 같을 수 있지만 다를 수 있는 상황에서는 다른 타입은 타입 변수를 하나만 쓰는게 아닌 두개를 쓰자.
<T, U> => 바꾸면서 (a: T, B:U)
function swap<T, U>(a: T, b: U): [U, T] {
return [b, a];
}
const [x, y] = swap("hello", 123); // [123, "hello"]
function returnFirstValue<T>(data: T[]): T {
return data[0];
}
const val = returnFirstValue([1, 2, 3]); // val: number
// 첫 번째 요소만 특정 타입으로 제한하고 싶을 때
function returnFirstValue<T>(data: [T, ...unknown[]]): T {
return data[0];
}
const val = returnFirstValue([1, "hello", true]); // val: number
// 첫 번째 요소만 특정 타입으로 제한하고 싶을 때
function returnFirstValue<T>(data: [T, ...unknown[]]): T {
return data[0];
}
const val = returnFirstValue([1, "hello", true]); // val: number
[T, ...unknown[]]: 첫 번째 요소는 T, 나머지는 어떤 타입이든 가능
첫 번째 요소만 타입을 고정하고 나머지는 무시 가능
let str = returnFirstValue([1,"hello","mynameis"]);
=> str의 타입 추론 결과는 string|number가 되며 T는 [number,string][] 튜플타입이 된다.
만약 첫번째 요소가 number로 타입을 추론하고 싶으면 <T>(data: [T, ...unknown[]])을 하게 되고 T첫번째 요소가 number이고 나머지는 몰라도 되는 것// length 속성을 가진 값만 허용
function getLength<T extends { length: number }>(data: T): number {
return data.length;
}
getLength([1, 2, 3]); // ✅ Array
getLength("hello"); // ✅ String
getLength({ length: 10 }); // ✅ 객체
getLength(123); // ❌ number는 length 속성이 없음
<T extends { length: number}>(data:T)
: T를 length 타입이 number 타입으로 가지고 있는 객체를 확장하는 타입으로 제한한다.
function map<T, U>(arr: T[], callback: (item: T) => U): U[] {
let result: U[] = [];
for (let i = 0; i < arr.length; i++) {
result.push(callback(arr[i]));
}
return result;
}
// 사용 예시
const doubled = map([1, 2, 3], (item) => item * 2); // number[]
const uppercased = map(["hi", "hello"], (item) => item.toUpperCase()); // string[]
function forEach<T>(arr: T[], callback: (item: T) => void): void {
for (let i = 0; i < arr.length; i++) {
callback(arr[i]);
}
}
// 사용 예시
forEach([1, 2, 3], (item) => {
console.log(item.toFixed()); // item은 number로 추론됨
});
| 항목 | 설명 |
|---|---|
T, U | 타입 변수 (Generic Type Parameters) |
<T> | 타입스크립트에서 제네릭 사용의 기본 문법 |
T extends ... | 타입 변수의 범위 제한 |
[T, ...unknown[]] | 첫 요소만 고정하고 나머지 무시 |
map, forEach | 고차 함수 구현에도 제네릭 활용 가능 |
제네릭 인터페이스, 제네릭 타입 별칭, 인덱스 시그니처, 제네릭 클래스, Promise 반환 타입 등 제네릭의 다양한 실전 활용 방법을 정리한 날입니다.
interface KeyPair<K, V> {
key: K;
value: V;
}
let keyPair : KeyPair<string,number> {
key: "key",
value:0,
}
// 제네릭 인터페이스를 사용할 때, 타입 변수에 할당할 타입을 꺽새와 함께 반드시 사용해야합니다.
타입변수
= 타입 파라미터
= 제네릭 타입 변수
= 제네릭 타입 파라미터
let keyPair: KeyPair<boolean, string[]> = {
key:ture,
value: ["1"],
}
interface NumberMap {
[key:string]: number;
}
interface Map<V>{
[key:string]: V
}
let stringMap: Map<string> = {
key:"value",
}
let booleanMap: Map<boolean> = {
key: true,
}
type Map2<V>{
[key:string]: V
}
let stringMap2 : Map2<string> = {
key:"",
}
interface Student {
type: "student";
school:string;
}
interface Developer {
type: "developer",
skill: string;
}
interface User<T>{
name:string;
profile: T;
}
const developerUser: User<Developer> ={
name:"김범수",
profile: {
type:"develper",
skill:"TypeScriptio"
}
}
const studentUser: User<Student>={
name:"김범수",
profile: {
type:"sutdnet",
school:"한림대"
}
}
function goToSchool(user: User<Student>){
if(user.profile.type !== "student"){
console.log("잘 못 오셨습니다.")
return;
}
const school = user.profile.school;
console.log(`${school}로 등교 완료`)
}
타입 좁히기 코드를 쓸 필요가 없음
class List<T> {
consturctor(private list: T[]){}
push(data: T){
this.list.pushdata()
}
pop(){
this.list.pop()
}
print(){
console.log(this.ist)
}
}
const numberList = new List([1,2,3]);
제네릭 클래스는 제네릭 인터페이스, 타입과 다르게 생성장에 인수로 전달하는 값을 기준으로 타입의 값을 추론한다.
const promise = new Promise<number>((resolve,reject) => {
setTimeout(() => {
resolve(20);
},3000)
promise.then((response) => {
console.log(response ); // 20
})
})
### 프로미스를 반환하는 함수의 타입을 정의
interface Post {
id: number;
title: string;
content: string;
}
function fetchPost(): Promise<Post>{
return new Promise((resolve,reject) =>{
setTimeout(() => {
resolve({
id: 1,
title: "게시글 제목",
content: "게시글 컨텐츠",
})
},3000)
})
}
const postRequest = fetchPost();
postRequest.then((post) => {
post.id
})
Indexed Access Type, keyof, Mapped Type, 템플릿 리터럴 타입 등 TypeScript의 고급 타입 조작 문법들을 정리한 날입니다.
객체, 배열, 튜플 타입에서 특정 속성의 타입을 추출하는 문법
interface Post {
title: string;
content: string;
author: {
id: number;
name: string;
age: number;
};
}
// 특정 속성의 타입을 추출
function printAuthorInfo(author: Post["author"]) {
console.log(author.name);
}
// 'author'는 변수, 값이 들어갈 수 없고 타입만 명시할 수 있다. ex) const key = "author" X 안됨
const post: Post = {
title:"게시글 제목",
content: "게시글 본문",
author: {
id:1,
name: "김범수"
age:27
}
}
id값 뽑아 오는 방법
Post["author"]["id"]
type PostList {
title:string;
content:string;
author: {
id: number;
name: string;
ag: number
}
}[];
const post:PostList[number] or PostList[0] = {
}
// 배열 타입으로부터 하나의 요소만 가져온다는 것.
type Tup = [number, string, boolean];
type First = Tup[0]; // number
type Second = Tup[1]; // string
type Union = Tup[number]; // number | string | boolean
type Tup3 = Tup[3] // X
객체 타입의 key들을 유니온 타입으로 반환하는 연산자
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // "name" | "age"
function getProperty(person: Person, key: keyof Person) {
return person[key];
}
const person: Person = {
name:"김범수",
age: 27
}
기존 타입을 바탕으로 속성 하나하나를 반복하며 변형
interface User {
id: number;
name: string;
age: number;
}
type BooleanUser = {
[key in keyof User]: boolean;
};
// 예제: 모든 속성을 readonly로 만들기
type ReadonlyUser = {
readonly [key in keyof User]: User[key];
};
// 예제: 선택적 속성으로 만들기 (Partial)
type PartialUser = {
[key in keyof User]?: User[key];
};
- 내장 제네릭 Partial<T> 와 동일합니다.
function updateUser(user: Partial<User>) {
// id, name, age 중 일부만 받아도 OK
}
문자열을 조합해서 새로운 문자열 타입을 생성할 수 있는 기능
type Color = "red" | "black" | "green";
type Animal = "dog" | "cat" | "chicken";
type ColoredAnimal = `${Color}-${Animal}`;
// "red-dog" | "red-cat" | "red-chicken" | "black-dog" | ...
문자열로 여러 상황들을 표현할 떄 사용된다.
| 문법 | 설명 |
|---|---|
T[K] | 특정 속성의 타입 추출 |
keyof T | 객체 타입의 key만 추출 |
[K in keyof T] | 속성 반복 → 변형 (Mapped Type) |
`${A}-${B}` | 문자열 조합으로 타입 생성 |
조건부 타입(T extends U ? A : B), 분산 조건부 타입, infer를 활용한 타입 추론 등 타입스크립트의 고급 타입 제어 문법을 집중적으로 학습한 날입니다.
type A = number extends string ? string : number; // A: number
type ObjA = { a: number };
type ObjB = { a: number; b: number };
type B = ObjB extends ObjA ? number : string; // B: number
A extends B ? C : D 구조로 작성됨
extends는 서브타입 관계를 검사
조건이 true면 C, false면 D를 결과 타입으로 반환
type StringNumberSwitch<T> = T extends number ? string : number;
let varA: StringNumberSwitch<number>; // string
let varB: StringNumberSwitch<string>; // number
function removeSpaces<T>(text: T): T extends string ? string : undefined;
function removeSpacesImpl(text: any) {
if (typeof text === "string") {
return text.replaceAll(" ", "") as any;
}
return undefined as any;
}
const result1 = removeSpacesImpl("hi im winterlood"); // string
const result2 = removeSpacesImpl(undefined); // undefined
type StringNumberSwitch<T> = T exjtends number ? string : number;
let varA : StringNumberSwitch<number> // string
let varB : StringNumberSwitch<string> // number
let c : StringNumberSwitch<number|string>;
// 이렇게 사용하면 한번은 StringNumberSwitch<number> 이렇게, 나머지 한번은 StringNumberSwitch<string> 이렇게 들어간다 그 결과를 유니온으로 묶는다.
그래서 결과가 타입은 string | number로 된다
T가 유니온 타입이면 조건부 타입이 각 요소에 개별적으로 적용됨
이걸 분산(distributed) 된다고 표현함
let d: StringNumberSwitch<boolean | number | string>;
// 1단계
StringNumberSwitch<boolean> |
StringNumberSwitch<number> |
StringNumberSwitch<string>
// 2단계
number |
string |
number
// 결과: number | string
ts
복사
편집
type NonDistributive<T> = [T] extends [number] ? string : number;
type A = NonDistributive<number | string>; // number
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<number | string | boolean, string>;
// 단계적으로 보면
// Exclude<number, string> → number
// Exclude<string, string> → never
// Exclude<boolean, string> → boolean
// 결과: number | boolean
type Extract <T, U> = T extends U ? T : never ;
type B = Extract<number | string | boolean, string>;
// 1 단계
// Extract<number, string> |
// Extract<string, string> |
// Extract<boolean, string>
// 2 단계
// never |
// string |
// never
// 최종 결과
// string
분산적인 조건부 타입을 막고 싶다면
[T] extends [number] ? string : number
// 이렇게 하면 분산적으로 가지 않는다.
조건부 타입 내에서 특정 타입만 추론해낼 수 있는 기능
type Func = () => string;
type FuncB = () => number;
type ReturnType<T> = T extends () => string ? string : never
type A = ReturnType<Func>; // string
type B = ReturnType<FuncB>; // never
// infer 사용
type ReturnType<T> = T extends () => infer R ? R : never -> R을 추론해라
() => string 이 infer R 타입에 서브 타입인지 확인하게 된다. 이때 infer R은 funcB의 () => number를 참으로 만들게 끔 동작합니다. 이때 R은 그러면 number로 추론이 된다.
type ReturnType<T> = T extends () => infer R ? R : never;
type A = ReturnType<() => string>; // string
type B = ReturnType<() => number>; // number
type C = ReturnType<number>; // never (함수가 아님)
type PromiseUnpack<T> = T extends Promise<infer R> ? R : never;
type P1 = PromiseUnpack<Promise<number>>; // number
type P2 = PromiseUnpack<Promise<string>>; // string
| 개념 | 설명 |
|---|---|
T extends U ? A : B | 조건에 따라 타입 분기 |
| 분산 조건부 타입 | 유니온 타입에 대해 분기 조건 개별 적용됨 |
Exclude<T, U> | T에서 U를 제거 |
Extract<T, U> | T에서 U만 추출 |
infer | 조건부 타입 내에서 특정 타입을 추론해서 사용 |
[T] extends [U] | 분산 조건부 타입 방지 패턴 |
TypeScript 내장 유틸리티 타입들의 기능과 원리(직접 구현)를 총정리한 날입니다.
기존 타입을 변형하거나 조합해 새로운 타입을 만들어내는 도구
✅ 맵드 타입 기반
<T><T><T><T, K><T, K><K, V>✅ 조건부 타입 기반
<T, U><T, U><T><T> 부분적인, 일부분의 라는 의미를 가지고 있습니다.
특정 객체 타입의 모든 프로퍼티를 선택적 프롭퍼티 바꿔주는 타입
interface Post {
title: string;
content: string;
tags: string[];
thumbnailUrl: string;
}
const draft: Partial<Post> = {
title: "임시 제목",
content: "초안입니다.",
};
type Partial<<T> = {
// 맵드 타입
[key in keyof T]?: T[key] // 인덱스드 엑세스타입
}
<T>"필수의", "필수적인" 이라는 의미를 가지고 있습니다.
특정 객체 타입의 모든 프로퍼티를 필수 프로퍼티로 바꿔주는 타입
// Required 직접 구현
type Required<T> = {
[K in keyof T]-?: T[K];
};
const completePost: Required<Post> = {
title: "완성된 글",
content: "내용",
tags: ["ts"],
thumbnailUrl: "https://...",
};
<T> - 읽기 전용 수정불가특정 객체 타입에서 모든 프로퍼티를 읽기 전용 프로퍼티로 만들어주는 타입
// 직접 구현
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
const readonlyPost: Readonly<Post> = {
title: "읽기 전용 글",
content: "수정 불가",
tags: [],
thumbnailUrl: "",
};
<T, K> -> 뽑다, 고르다객체 타입으로부터 특정 프로퍼티만 딱 골라내는 그런 타입
interface Post {
title:String;
tags: string[];
content: string;
thumbnailUrl?: string;
}
// 직접 구현
type Pick<T,K extends keyof T> = {
[key in K] : T[key]
}
const legacyPost: Pick<Post,"title"|"content"> = {
title:"엣날 글",
content: "옛날 컨텐츠",
}
K에는 제약을 걸어놓아야 한다. 그렇지 안흥면 함수타입 number 타입 등 여러 타입들이 들어올 수 있게 됩니다.
K에는 반드시 T의 key만 올 수 있도록 extends keyof T로 제약을 걸어야 합니다.
K는 T값에서 추출한 유니온 타입인 서브타입만 들어올 수 있게 때문이다.
풀어서 설명해보자.
T에 Post에 들어가게 된다면
(extends 기준으로) 좌측은 우측의 서브타입이기 때문에 가능하다. 그리고 K에는 T 객체 프로퍼티의 키만 전달할 수 있도록 해야한다.
<T,K> -> 생략하다, 빼다객체 타입으로부터 특정 프로퍼티를 제거하는 타입
// 직접 구현하기
type Omit<T, K exnteds typeof T> = Pick<T, Exclude<keyof T, K>>;
// 풀어서 적어보기
Pick<Post, Exclude<keyof Post, 'title'>>
Pick<Post, Exclude<keyof 'title' | 'tags' | 'content' | 'thumbnailURL', 'title'>>
Pick<Post, 'content' | 'tags' | 'thumbnailURL'>>
const noTitlePost: Omit<Post, "title"> = {
content: "제목 없음",
tags: [],
thumbnailUrl: "",
};
<K, V>K를 key로, V를 value로 갖는 객체 타입 생성
type ThumbnailLegacy = {
large: {
url: string;
};
medium: {
url: string;
};
small: {
url: string;
}
watch: {
url: string
}
}
type Thumbnail = Record<"large"| "mediumm" | "small" | "watch", { url:string }>;
// 직접 구현
type Thumbnail = Record<K extends keyof any, V> = {
[key in K]: V;
}
<T, U> -> 제외하다, 추방하다T에서 U를 제거하는 타입
type A = Exclude<string | boolean, boolean> => string
// 직접 구현
type Exclude<T,U> = T extends U ? never : T;
<T, U>T에서 U를 추출합는 타입
// 직접 구현
type Extract<T, U> = T extends U ? T : never
함수의 반환값 타입을 추출하는 타입
function funcA(){
return "hello";
}
function funcB(){
return 10;
}
type ReturnA = ReturnType<typeof funcA> // string
type ReturnB = ReturnType<typeof funcB> // number
// 직접 구현
type ReturnType<T extends (...args: any) => any> = T extends (... args : any) => infer R ? R : never;
퇴근 후 짬을 내어 수업을 듣고 한입 챌린지 6기를 완주했다는 사실에 스스로도 뿌듯함을 느낍니다. (정리하다보니 두서없이 작성한 것 같아 보시는데 불편함이 있으실까 걱정이 됩니다.🥹 )
이번 과정을 통해 실무에서 다른 분들의 타입스크립트 설계를 보며 자극을 받았고,
저 또한 더 효율적이고 유연하게 타입을 설계하는 법에 대해 많이 고민하게 되었습니다.
Day 1️⃣부터 Day 1️⃣4️⃣까지의 내용을 정리하면서, 단순히 문법을 익히는 것을 넘어
실무에서 마주했던 애매한 타입 설계 문제들을 스스로 해석하고 해결할 수 있는 기준이 생겼습니다.
글로 풀어내는 과정 속에서 표면적인 이해를 넘어 개념의 맥락과 의미를 되짚어보는 시간이었습니다.☺️
끝까지 읽어주셔서 감사합니다. 🙇♂️
강의: 한 입 크기로 잘라먹는 TypeScript
타입 계층도 이미지 출처: 한 입 크기로 잘라먹는 타입스크립트(TypeScript)