WeniVooks

검색

타입스크립트 베이스캠프

타입 관련 고급 기능

1. 타입 단언과 널 처리

1.1 Non-Null 단언 연산자

JavaScript에서는 DOM 요소를 선택할 때 해당 요소가 없을 수 있어서 null을 반환할 수 있습니다. TypeScript에서는 이런 상황에서 ! 연산자를 사용하여 "이 값은 절대 null이 아닙니다"라고 컴파일러에게 알려줄 수 있습니다.

// 기본적인 예제
const $button = document.querySelector('button');
// 이 시점에서 TypeScript는 $button이 null일 수 있다고 생각합니다
// $button.click(); // 에러 발생!
 
// Non-Null 단언 사용
const $safeButton = document.querySelector('button')!;
$safeButton.click(); // 정상 작동
// 기본적인 예제
const $button = document.querySelector('button');
// 이 시점에서 TypeScript는 $button이 null일 수 있다고 생각합니다
// $button.click(); // 에러 발생!
 
// Non-Null 단언 사용
const $safeButton = document.querySelector('button')!;
$safeButton.click(); // 정상 작동
1.2 타입 단언(Type Assertion)

때로는 우리가 TypeScript보다 어떤 값의 타입을 더 정확히 알고 있는 경우가 있습니다. 이럴 때 as 키워드를 사용하여 타입을 명시적으로 지정할 수 있습니다. 다만 타입 단언은 매우 주의해야 하는데, 타입 단언을 할 경우 런타임과 컴파일 모두 유형 검사를 우회하게 됩니다. 따라서 타입 단언을 남발하면 타입 안전성이 떨어질 수 있습니다.

그러면 타입 단언은 언제, 왜 사용해야 할까요? 타입 단언을 사용하게 되면 사람과 컴퓨터 모두가 타입을 인식하기 좋습니다. 예를 들어, IDE 같은 경우 자동완성 기능을 제공할 때 타입 단언을 사용하면 더 정확한 정보를 제공할 수 있습니다. 사람이 읽을 때에도 타입 단언을 사용하면 코드를 더 쉽게 이해할 수 있습니다.

// 이벤트 핸들러 예제
document.querySelector('input')!.addEventListener('change', (e) => {
    // e.target은 기본적으로 EventTarget 타입
    const target = e.target as HTMLInputElement;
    console.log(target.value); // .value 접근 가능
 
    // as 없이 작성한다면:
    // const target = e.target;
    // console.log(target.value); // 에러 발생!
});
 
// 실제 사용 예제
interface User {
    name: string;
    age: number;
}
 
const userJSON = '{"name": "John", "age": 30}';
const user = JSON.parse(userJSON) as User;
console.log(user.name); // 타입 안전성 확보
// 이벤트 핸들러 예제
document.querySelector('input')!.addEventListener('change', (e) => {
    // e.target은 기본적으로 EventTarget 타입
    const target = e.target as HTMLInputElement;
    console.log(target.value); // .value 접근 가능
 
    // as 없이 작성한다면:
    // const target = e.target;
    // console.log(target.value); // 에러 발생!
});
 
// 실제 사용 예제
interface User {
    name: string;
    age: number;
}
 
const userJSON = '{"name": "John", "age": 30}';
const user = JSON.parse(userJSON) as User;
console.log(user.name); // 타입 안전성 확보

💡 주의사항: 타입 단언은 필요한 경우에만 사용해야 합니다. 과도한 사용은 타입 안전성을 해칠 수 있습니다.

2. 객체 타입 고급 기능

2.1 인덱스 시그니처

객체의 속성 이름을 미리 알 수 없을 때 유용한 기능입니다. 주로 동적 데이터를 다룰 때 사용됩니다.

// 기본 예제
interface Dictionary {
    [key: string]: string;
}
 
const colors: Dictionary = {
    red: "#ff0000",
    blue: "#0000ff",
    green: "#00ff00"
};
 
// 실제 활용 예제
interface UserData {
    id: number;
    name: string;
    [key: string]: string | number | boolean; // 다양한 타입 허용
}
 
const userData: UserData = {
    id: 1,
    name: "Alice",
    age: 25,
    isAdmin: true,
    department: "Engineering"
};
// 기본 예제
interface Dictionary {
    [key: string]: string;
}
 
const colors: Dictionary = {
    red: "#ff0000",
    blue: "#0000ff",
    green: "#00ff00"
};
 
// 실제 활용 예제
interface UserData {
    id: number;
    name: string;
    [key: string]: string | number | boolean; // 다양한 타입 허용
}
 
const userData: UserData = {
    id: 1,
    name: "Alice",
    age: 25,
    isAdmin: true,
    department: "Engineering"
};
2.2 keyof 연산자

객체의 모든 키를 타입으로 추출할 때 사용합니다. 특히 제네릭과 함께 사용할 때 유용합니다. 이 연산자를 사용하면 객체의 키를 문자열로 추출할 수 있습니다. 간단한 예제부터 살펴보도록 하겠습니다.

interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
}
 
// Product의 모든 키를 타입으로 추출
type ProductKeys = keyof Product; // "id" | "name" | "price" | "category"
 
const data: ProductKeys = "id"; 
// 여기에 "name", "price", "category" 등 다른 값을 사용할 수 있습니다.
// 다만 1000, "hello" 등의 key가 아닌 경우에는 사용할 수 없습니다.
 
console.log(data); // id
interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
}
 
// Product의 모든 키를 타입으로 추출
type ProductKeys = keyof Product; // "id" | "name" | "price" | "category"
 
const data: ProductKeys = "id"; 
// 여기에 "name", "price", "category" 등 다른 값을 사용할 수 있습니다.
// 다만 1000, "hello" 등의 key가 아닌 경우에는 사용할 수 없습니다.
 
console.log(data); // id

이는 아래와 같은 방식으로 사용할 수 있게 해줍니다.

interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
}
 
// Product의 모든 키를 타입으로 추출
type ProductKeys = keyof Product; // "id" | "name" | "price" | "category"
 
// 실제 활용 예제
function getProductValue(product: Product, key: keyof Product) {
    return product[key];
}
 
const laptop: Product = {
    id: 1,
    name: "노트북",
    price: 1000000,
    category: "전자기기"
};
 
console.log(getProductValue(laptop, "name")); // "노트북"
console.log(getProductValue(laptop, "invalid")); // 컴파일 에러!
interface Product {
    id: number;
    name: string;
    price: number;
    category: string;
}
 
// Product의 모든 키를 타입으로 추출
type ProductKeys = keyof Product; // "id" | "name" | "price" | "category"
 
// 실제 활용 예제
function getProductValue(product: Product, key: keyof Product) {
    return product[key];
}
 
const laptop: Product = {
    id: 1,
    name: "노트북",
    price: 1000000,
    category: "전자기기"
};
 
console.log(getProductValue(laptop, "name")); // "노트북"
console.log(getProductValue(laptop, "invalid")); // 컴파일 에러!

다만 이렇게 사용했을 경우 일반적으로 laptop["name"]과 같이 직접 접근하는 것이 더 간단할 수 있습니다. 하지만 keyof 연산자는 복잡한 타입에서 사용할 때 유용합니다.

3. 연습문제

  1. Non-Null 단언 문제

    // TODO: Non-Null 단언을 사용하여 아래 코드를 완성하세요.
    const $input = document.querySelector('input');
    // $input의 value를 콘솔에 출력하세요.
    // TODO: Non-Null 단언을 사용하여 아래 코드를 완성하세요.
    const $input = document.querySelector('input');
    // $input의 value를 콘솔에 출력하세요.
  2. 타입 단언 문제

    // TODO: 아래 JSON 데이터를 파싱하고 타입 단언을 사용하여
    // name과 age를 안전하게 접근할 수 있도록 만드세요.
    const data = '{"name": "Jane", "age": 25}';
    // TODO: 아래 JSON 데이터를 파싱하고 타입 단언을 사용하여
    // name과 age를 안전하게 접근할 수 있도록 만드세요.
    const data = '{"name": "Jane", "age": 25}';
  3. 인덱스 시그니처 문제

    // TODO: 학생의 과목별 성적을 저장할 수 있는 인터페이스를 만드세요
    // 학생의 이름은 필수이며, 나머지 과목들은 동적으로 추가될 수 있습니다
    // TODO: 학생의 과목별 성적을 저장할 수 있는 인터페이스를 만드세요
    // 학생의 이름은 필수이며, 나머지 과목들은 동적으로 추가될 수 있습니다
  4. keyof 연산자 문제

    interface Car {
        brand: string;
        model: string;
        year: number;
    }
    // TODO: Car 인터페이스의 속성 중 하나를 인자로 받아
    // 해당 속성의 값을 반환하는 함수를 작성하세요.
    interface Car {
        brand: string;
        model: string;
        year: number;
    }
    // TODO: Car 인터페이스의 속성 중 하나를 인자로 받아
    // 해당 속성의 값을 반환하는 함수를 작성하세요.

4. 연습문제 해답

  1. Non-Null 단언 해답

    const $input = document.querySelector('input')!;
    console.log($input.value);
    const $input = document.querySelector('input')!;
    console.log($input.value);
  2. 타입 단언 해답

    interface Person {
        name: string;
        age: number;
    }
    const data = '{"name": "Jane", "age": 25}';
    const person = JSON.parse(data) as Person;
    console.log(person.name, person.age);
    interface Person {
        name: string;
        age: number;
    }
    const data = '{"name": "Jane", "age": 25}';
    const person = JSON.parse(data) as Person;
    console.log(person.name, person.age);
  3. 인덱스 시그니처 해답

    interface StudentGrades {
        name: string;
        [subject: string]: string | number; // 과목명: 점수
    }
     
    const studentGrades: StudentGrades = {
        name: "김철수",
        math: 95,
        english: 88,
        science: 92
    };
    interface StudentGrades {
        name: string;
        [subject: string]: string | number; // 과목명: 점수
    }
     
    const studentGrades: StudentGrades = {
        name: "김철수",
        math: 95,
        english: 88,
        science: 92
    };
  4. keyof 연산자 해답

    interface Car {
        brand: string;
        model: string;
        year: number;
    }
     
    function getCarProperty(car: Car, property: keyof Car) {
        return car[property];
    }
     
    const myCar: Car = {
        brand: "Cats Motors",
        model: "Licat1",
        year: 2224
    };
     
    console.log(getCarProperty(myCar, "brand")); // "Cats Motors"
    console.log(myCar['brand']); // "Cats Motors"
    // 간단한 타입의 경우에는 myCar['brand']와 같이 직접 접근해도 크게 문제가 없지만
    // 복잡한 타입의 경우에는 keyof를 사용하는 것이 안전합니다.
    interface Car {
        brand: string;
        model: string;
        year: number;
    }
     
    function getCarProperty(car: Car, property: keyof Car) {
        return car[property];
    }
     
    const myCar: Car = {
        brand: "Cats Motors",
        model: "Licat1",
        year: 2224
    };
     
    console.log(getCarProperty(myCar, "brand")); // "Cats Motors"
    console.log(myCar['brand']); // "Cats Motors"
    // 간단한 타입의 경우에는 myCar['brand']와 같이 직접 접근해도 크게 문제가 없지만
    // 복잡한 타입의 경우에는 keyof를 사용하는 것이 안전합니다.
3.3 타입 가드3.5 비동기