TypeScript Exercises 풀이노트

nnhw·2025년 4월 6일
2
post-thumbnail

Typescript Exercises 사이트 바로가기

타입스크립트를 처음 접했을 때 가장 막막했던 건,
"이걸 어디서부터 어떻게 적용해야 하지?" 였습니다.

하지만 이전에 자바스크립트 외 다른 언어(Java, C 등)를 접해본 분이라면,
이 exercise를 풀어보고, 여기서 제공해주는 공식 핸디북만으로도 타입스크립트를 프로젝트에 적용하는 데 충분하다고 느꼈어요.

저도 아직 타입스크립트를 능숙하게 다루는 단계는 아니지만,
공부하면서 정리해두면 좋겠다고 생각한 개념들과 풀이를 노트처럼 기록해보았습니다.

아직 14단계까지는 다 풀지 못했지만..! 💦 공부하면서 천천히 채워나갈 예정이에요.
함께 배우고 성장해봐요! 👩‍💻🌱

1,2번 문제는 따로 풀이를 적지 않았습니다!!


Exercise 3 : narrowing using in operator

📘 in-operator-narrowing 핸디북

  • 여러 타입이 가능한 경우, 조건문을 통해 특정 타입으로 좁혀서 작업할 수 있음
  • 이때 in 연산자를 사용해 객체에 특정 속성이 존재하는지 검사하면 타입을 구분할 수 있음
📂 예제 코드 보기

✅ 예제 1: 기본적인 타입 좁히기

type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim();
  }
  return animal.fly();
}
  • animalFish 또는 Bird
  • "swim" in animal이면 → Fish로 좁혀짐
  • 아니면 → Bird로 좁혀짐
    👉 속성의 존재 여부로 타입을 좁힘

✅ 예제 2: optional 속성 포함된 타입

type Human = { swim?: () => void; fly?: () => void };
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird | Human) {
  if ("swim" in animal) {
    // animal: Fish | Human
  } else {
    // animal: Bird | Human
  }
}
  • Humanswim, fly 둘 다 선택적(optional) 속성
  • "swim" in animal이면 → Fish | Human
  • "swim" not in animal이면 → Bird | Human
    ✅ 즉, optional 속성은 양쪽 케이스에 다 걸릴 수 있음!
📂 최종 코드 예시 보기
export function logPerson(person: Person) {
    let additionalInformation: string;
    // **in** 이라는 키워드를 사용해서 타입 좁히기
    if ('role' in person) {
        additionalInformation = person.role;
    } else {
        additionalInformation = person.occupation;
    }
    console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

Exercise 4 : narrowing using type predicates

📘 type-predicates 핸디북

  • 더 세밀하게 직접 타입을 좁히고 싶을 때, 타입 가드 함수를 만들 수 있다.
  • Admin 타입과 User 타입 모두를 받는 Person 타입 변수에서 Admin만 골라내고 싶다면,
    타입 가드 함수 isAdmin을 만들고, person is Admin 형태로 명시한다.
📂 타입 가드 예시 보기
export function isAdmin(person: Person): **person is Admin** {
    return person.type === 'admin';
}

export function isUser(person: Person): person is User {
    return person.type === 'user';
}

Exercise 5: Utility Types

📘 utility-types 핸디북

  • Partial<Type>
    👉 해당 타입의 일부 속성만 사용할 수 있게 만들고 싶을 때 사용

🔧 그 외 다양한 Utility Types

  • Required<T> : 모든 속성을 필수로
  • Readonly<T> : 모든 속성을 읽기 전용으로
  • Pick<T,K> : 특정 속성만 골라서 사용
  • Omit<T,K> : 특정 속성을 제외하고 사용
  • Record<K,T> : K를 key로, T를 value 타입으로 갖는 객체 생성
  • Exclude<T,U> : T에서 U를 제외
  • Extract<T,U> : T에서 U와 겹치는 타입만 추출
📂 예제 코드 보기
export function filterUsers(persons: Person[], criteria: **Partial<User>**): User[] {
    return persons.filter(isUser).filter((user) => {
        const criteriaKeys = Object.keys(criteria) as (keyof User)[];
        return criteriaKeys.every((fieldName) => {
            return user[fieldName] === criteria[fieldName];
        });
    });
}

console.log('Users of age 23:');

filterUsers(
    persons,
    {
        age: 23
    }
).forEach(logPerson);

Exercise 6 : Function Overloads

  • 하나의 함수가 다양한 타입의 매개변수와 반환값을 가질 수 있도록 하는 기능
  • 시그니처 선언부구현부를 분리해서 작성함

📌 예시

📂 오버로드 시그니처 & 구현 예시
// 시그니처 선언
function fn(x: string): void;
function fn(x: number): void;

// 실제 구현부 (모든 경우 커버)
function fn(x: string | number) {
  console.log(x);
}
📂 filterPersons 예제 코드
// 시그니처 선언
export function filterPersons(persons: Person[], personType: 'user', criteria: Partial<User>): User[];
export function filterPersons(persons: Person[], personType: 'admin', criteria: Partial<Admin>): Admin[];

// 구현부
export function filterPersons(persons: Person[], personType: 'user' | 'admin', criteria: Partial<Person>): Person[] {
    return persons
        .filter((person) => person.type === personType)
        .filter((person) => {
            const criteriaKeys = Object.keys(criteria) as (keyof Person)[];
            return criteriaKeys.every((fieldName) => {
                return person[fieldName] === criteria[fieldName];
            });
        });
}

Exercise 7 : Tuple, Generic

📘 tuple-types 핸디북
📘 generics 핸디북

  • Tuple: 길이와 각 위치의 타입이 정해진 배열
    • 예: type Pair = [string, number]
  • Generic: 타입을 변수처럼 받아서 재사용할 수 있게 함
📂 튜플 + 제네릭 함수 예시
export function swap<T, U>(...args: [T, U]): [U, T] {
  const [v1, v2] = args;
  return [v2, v1];
}

Exercise 8 : Intersection Types (&)

📘 intersection-types 핸디북

  • 인터섹션 타입(&): 여러 타입을 합친 새로운 타입을 생성
  • 아래 예시는 UserAdmin 타입을 결합해서 PowerUser 타입 생성
📂 PowerUser 타입 예시
type PowerUser = Omit<User & Admin, 'type'> & {
  type: 'powerUser';
};

Exercise 9 : 모듈 선언 보강 (Ambient Module)

📘 ambient-modules 핸디북

  • 타입 정의가 없는 외부 모듈에 대해 타입 정보를 직접 선언해줄 수 있음
  • declare module 구문 사용
📂 str-utils 모듈 타입 선언
declare module 'str-utils' {
  export function strReverse(value: string): string;
  export function strToLower(value: string): string;
  export function strToUpper(value: string): string;
  export function strRandomize(value: string): string;
  export function strInvertCase(value: string): string;
}

Exercise 10 : Promisify 함수 만들기

  • 콜백 기반 API를 Promise로 바꿔주는 고차 함수
  • promisify() 함수는 callback을 받아 내부에서 resolve/reject 처리함
📂 promisify 함수 예시
export function promisify<T>(
  arg: (callback: (response: ApiResponse<T>) => void) => void
): () => Promise<T> {
  return () => new Promise<T>((resolve, reject) => {
    arg((res) => {
      if (res.status === 'success') {
        resolve(res.data);
      } else {
        reject(new Error(res.error));
      }
    });
  });
}
📂 사용 예시
const oldApi = {
  requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
    callback({ status: 'success', data: admins });
  },
};

export const api = {
  requestAdmins: promisify(oldApi.requestAdmins),
};

await api.requestAdmins().then(console.log);
const oldApi = {
  requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
    callback({ status: 'success', data: admins });
  },
};

export const api = {
  requestAdmins: promisify(oldApi.requestAdmins),
};

await api.requestAdmins().then(console.log);

Exercise 11 : Ambient Module 선언

📘 ambient-modules 핸디북

  • declare module "모듈명" 구문을 통해, 실제 존재하는 외부 모듈을 타입스크립트에 알려줌
  • 보통 .d.ts 파일에서 선언하고, 타입 자동완성/체크에 도움을 줌
📂 str-utils 모듈 타입 정의
import {
  strReverse,
  strToLower,
  strToUpper,
  strRandomize,
  strInvertCase
} from 'str-utils';
declare module 'str-utils' {
  export function strReverse(value: string): string;
  export function strToLower(value: string): string;
  export function strToUpper(value: string): string;
  export function strRandomize(value: string): string;
  export function strInvertCase(value: string): string;
}

Exercise 12 : 타입 정의가 없는 외부 모듈 직접 선언

  • 외부 모듈이 타입을 제공하지 않는 경우, 직접 타입 정의 파일을 만들어야 함
📂 stats 모듈 타입 정의
declare module 'stats' {
  export function getMaxIndex<T>(input: T[], comparator: (a: T, b: T) => number): number;
  export function getMaxElement<T>(input: T[], comparator: (a: T, b: T) => number): null | T;
  export function getMinIndex<T>(input: T[], comparator: (a: T, b: T) => number): number;
  export function getMinElement<T>(input: T[], comparator: (a: T, b: T) => number): null | T;
  export function getMedianIndex<T>(input: T[], comparator: (a: T, b: T) => number): number;
  export function getMedianElement<T>(input: T[], comparator: (a: T, b: T) => number): null | T;
  export function getAverageValue<T>(input: T[], getValue: (a: T) => number): null | number;
}
📂 stats 모듈 내부 구현 보기
function getMaxIndex(input, comparator) {
  if (input.length === 0) return -1;
  let maxIndex = 0;
  for (let i = 1; i < input.length; i++) {
    if (comparator(input[i], input[maxIndex]) > 0) {
      maxIndex = i;
    }
  }
  return maxIndex;
}

function getMaxElement(input, comparator) {
  const index = getMaxIndex(input, comparator);
  return index === -1 ? null : input[index];
}

// ... (다른 함수도 유사한 로직)

module.exports = {
  getMaxIndex,
  getMaxElement,
  getMinIndex,
  getMinElement,
  getMedianIndex,
  getMedianElement,
  getAverageValue
};

Exercise 13 : Module Augmentation (모듈 확장)

📘 module-augmentation 핸디북

  • 기존 모듈의 타입을 덮지 않고 확장하는 기능
  • declare module 내에서 interface를 다시 선언하면 병합됨
📂 dateWizard 네임스페이스 확장
declare namespace dateWizard {
  interface DateDetails {
    year: number;
    month: number;
    date: number;
  }

  function dateDetails(date: Date): DateDetails;
  function utcDateDetails(date: Date): DateDetails;
}

14부터는 뭔소리인지 도저히 모르겠더라고요 ..
공부를 더 하고 다시 추가하러 오도록 하겠습니닷 !!!!!

profile
웹 프론트엔드 취준생 🥔

0개의 댓글