[TS] 타입스크립트와 DOM

박하늘·2025년 4월 11일

▪️ DOM

  • DOM (문서 객체 모델 - Document Object Model)
  • HTML 요소에 접근하기 위해 사용 ( JS 에서 제공되는 모든 DOM API 를 그대로 사용 )
  • 타입스크립트 자체적으로 DOM API 의 타입 제공
  • 타입 정의 파일 제공 lib.dom.d.ts => HTMLElement 를 입력 후 커맨드 클릭 하여 접속

📍 예제

[ 일반 ]

// 예제 코드
// <a id="myLink" href="https://google.com"> google </a>

const link1 = document.getElementById('myLink')     // link1 = [ HTMLElement | null ] 으로 타입추론
const link2 = document.querySelector('#myLink')     // link2 = [ Element | null ] 으로 타입추론

link1.href = "" // link1 오류 : null 값일 수 있음 경고 , 
                // href 오류 : 해당 메서드는 AnchorElement 만 가능하지만 현재 link1 은 [ HTMLElement | null ] 으로 타입추론 상태

// [1] if 문 적용 : link1 의 null 값 여부 확인 → 이렇게 적용해도 href에 오류는 그대로 존재
if(link1){
    link1.href = ""
}

// [2] 오류 모두 해결 방안

// [ 2-1 ] instanceof 사용 : link1 이 HTMLAnchorElement 타입인지 확인
if(link1 instanceof HTMLAnchorElement){
    link1.href = "https://google.ca"
}

// [ 2-2 ] DOM 을 선언할 때부터 as로 타입 단언 해주기
const link11 = document.getElementById('myLink') as HTMLAnchorElement
// 이렇게 바로 사용 가능
link11.href = "https://google.ca"


// [ 이 외 ]

// 📍 createElement
const img = document.createElement('img')
// 이렇게 바로 접근 가능
img.src = ""

// 📍 querySelector
const div = document.querySelector('div');

// 이렇게도 가능 ~!
const buttons = document. querySelectorAll('button');

buttons.forEach(button=>{
    button. click();
})
  • 📍 createElement : 매개변수로 HTML의 태그 이름을 전달을 함으로써 그 요소를 찾아서 일치하는 타입이 있으면 그 타입을 반환하되 없을 경우 일반적인 HTMLElement 를 반환

  • 📍 querySelector : div 는 [ HTMLDivElement | null ] 으로 타입 추론 , 역시나 null 값이 포함된 유니온 타입임으로 이 부분은 주의 필요

[ event ]

// <div id="myDiv"></div>
const myDiv = document.getElementById("myDiv");

// (e) : 아무것도 넣지 않으면 자동적으로 MouseEvent 타입으로 추론
// (e: MouseEvent | KeyboardEvent) : 유니언 타입 지정 가능
// (e: Event) : 더 폭 넓게 지정하여 조건문으로 완성
myDiv?.addEventListener('click', (e: Event)=>{
  
    // mouse event
    if(e instanceof MouseEvent) {
        //로직 구현
        const x = e.clientX;
        const y = e.clientX;
    }

    // keyboard event
    if(e instanceof KeyboardEvent) {
     console.log(e.code);
    }
})

DOM 인터페이스 관련 문서

https://developer.mozilla.org/ko/docs/Web/API
https://developer.mozilla.org/ko/docs/Web/API/HTMLElement
https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts

▪️ Type Narrowing ( 타입 좁히기 )

  • 해당 변수의 가능한 타입을 보다 구체적으로 좁혀나가는 과정
  • typeof, instanceof, 동일성, 사용자 정의 가드 등을 사용
  • 함수가 여러 타입의 매개 변수를 받아 각 타입에 맞는 적절한 처리를 할 때 주로 사용

📍 예제

[ typeof ]

type Id = number | string;

let id: Id = 1;

function getId(id: Id){
    if(typeof id === 'number'){
        return id
    }

    return Number(id)
}

getId(1)
getId("2")
  • typeof 를 사용하여 해당 값의 타입을 확인하여 조건문 설정

[ 동일성/동등성 좁히기 ( equality narrowing ) ]

type Power = "on" | "off"

function power(option: Power) {
    if (option === "off"){
        console.log("power off")
    } else{
        console.log("power on")
    }
}

power("on")
power("off")
  • typeof 와 다른 점은 typeof 는 타입의 동등성이지만, equality narrowing은 값의 동등성으로 판별

[ in ]

type iOS = { iMessage: ()=> void; }
type android = { message: () => void; }

function sendMessage(os: iOS | android) {
    // 예를 들어 os 중에 type iOS 면 iMessage 가 포함된 거니까 true 코드가 실행 돼
    if("iMessage" in os) {
        os.iMessage(); // 1OS 로 좁혀진다
    } else {
        os.message(); // android 케이스
    }
}
sendMessage ({iMessage: ()=> { console.log("sending iMessage")}}) // ios 타입
sendMessage({message: () => { console.log("sending message")}}) // android 타입

[ instanceof narrowing ]

class ApiReponse {
    data: any;
}

class ErrorResponse { 
        message: string;
}

async function handleApiResponse (response: any) {
    if(response instanceof ApiReponse) {
        // 데이터 처리
    } else if (response instanceof ErrorResponse) {
        // 에러 처리
    }
}

const apiResponse = new ApiReponse;
const errorResponse = new ErrorResponse;

handleApiResponse(apiResponse); 
handleApiResponse(errorResponse);

[ 타입 가드 (type predicates) ]

function isErrorReponse(response: ApiReponse | ErrorResponse): response is ErrorResponse {
    return (response as ErrorResponse).message !== undefined;
}

const response = { message: "error.." };

if(isErrorReponse (response)) {
    // 에러 케이스
    console. log (response.message);
}
  • response is ErrorResponse : true면 response가 ErrorResponse로 타입 좁혀져요! 라는 약속
    즉, 이 함수가 true를 리턴하면, response는 ErrorResponse

  • response as ErrorResponse : 강제로 ErrorResponse처럼 취급해보고 message 속성이 있는지 확인해
    만약 message가 있다면 ErrorResponse라고 판단해서 true를 반환
    여기서 (response as ErrorResponse)는 response를 임시로 ErrorResponse라고 간주하고, 그 안에 message라는 속성이 존재하는지 확인
    이 코드에서는 response 안에 message라는 속성이 있으니까 true

[ 식별 가능한 유니언 타입 (discriminated union) ]

type SuccessResponse = {
    type: "success", 
    data: any;
}
type ErrorResponseType = {
    type: "error", 
    message: string;
}

type ApiResponseType = SuccessResponse | ErrorResponseType;

function handleResponse(response: ApiResponseType) {
    if(response.type === "success") {
        console.log ('data:', response.data) ;
    } else {
        console.log (response.message);
    }
}

handleResponse({
    type: "success", 
    data: "성공하였습니다"
})                        // data: 성공하였습니다

▪️ Advanced Types ( 고급 타입 )

기본 타입을 확장하여 복잡한 타입 관계와 구조를 표현할 수 있게 해주는 다양한 타입 기능

  • 교차 타입 (intersection type)
  • 조건부 타입 (conditional type)
  • keyof 연산자 타입
  • 매핑 타입 (mapped type)

📍 예제

[ 교차 타입 (Intersection type) ]

type A = { name: string }
type B = { age: number }

type Person = A & B;

const person: Person = {
    name: 'John',
    age: 33
}

//-----------------------------------------------------------------------

type UserBase = { id: number };
type WithName = { name: string };
type WithEmail = { email: string };
type WithAge = { age: number };

type GuestUser = UserBase & WithName;
type User = UserBase & WithName & WithEmail & WithAge;

const guest: GuestUser = {
    id: 100, 
    name: "Paul"
}

const user: User = {
    id: 123, 
    name: "Lee" ,
    age: 30, 
    email: "test@email.com"
}

[ 조건부 타입 (conditional type) ]

  • 기본 문법
    // T 가 extends 를 사용하여 U 를 확장하면 X 아니면 Y
    type ConditionalType = T extends U ? X : Y;
type IsNumber<T> = T extends number ? "Yes" : "No";

type Result1 = IsNumber<number> // "Yes"
type Result2 = IsNumber<string> // "No"

type JsonOrText<T extends "json" | "text"> = T extends "json" ? object : string;

type JsonResponse = JsonOrText<"json"> // object
type TextReponse = JsonOrText<"text"> // string

[ keyof 연산자 타입 ]

type MyObject = {
    a: number;
    b: string;
    c: boolean;
}

type Keys = keyof MyObject;  // Keys = "a" | "b" | "c"

// T: 객체 전체 타입 / K: T의 키 중 하나 (K extends keyof T)
// 즉, key는 반드시 obj 안에 존재하는 키만 받을 수 있게 타입 제한
function getProp<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}
const obj = { x: 10, y: 20, z: 15 };

const value = getProp (obj, "y"); // 20 반환
  • keyof : Myobject의 key 를 union 타입으로 추출

[ mapped type (매핑 타입) ]


// type Mapped<T> = {
// 		[P in keyof T]: T[P]
// }


type OptionalType<T> = {
    [P in keyof T]?: T[P]
}

type ReadonlyType<T> = {
    readonly [P in keyof T]: T[P]
}

type UserType = {
    id: number;
    name: string;
    age: number; 
    email: string;
    }

// UserType 의 모든 속성을 [ 선택적 ] 으로 변환
// OptionalUserType에 마우스 호버 시 모든 속성 뒤에 ? 붙음
type OptionalUserType = OptionalType<UserType>

// UsenType 의 모든 속성을 [ 읽기 전용 ] 으로 변환
// ReadonLyUserType에 마우스 호버 시 모든 속성 앞에 readonly 붙음
type ReadonLyUserType = ReadonlyType<UserType>

Utility Types

유틸리티 타입 설명

  • Partial<T> : 타입 T 의 모든 속성을 선택적으로 만든다
  • Readonly<T> : 타입 T 의 모든 속성을 읽기 전용으로 만든다
  • Pick<T, K> : 타입 T에서 속성 K 만을 선택하여 타입을 생성
  • Omit<T, K> : 타입 T에서 속성 K를 제외한 타입을 생성
  • Record<K, T> : K 타입의 키와 T 타입의 값으로 구성된 타입을 생성
  • Parameters<T> : 함수 타입 T 의 매개변수 타입들을 튜플 타입으로 추출

타입스크립트가 제공하는 모든 유틸리티 타입

https://www.typescriptlang.org/docs/handbook/utility-types.html

📍 예제

[ Partial<T> ]

type User = {
    id: number; 
    name: string; 
    email: string;
}

type PartialUser = Partial<User>
  • User의 속성을 모두 옵셔널로 바꿔준다
  • PartialUser 에 마우스 호버 시 모든 속성 뒤에 ? 붙음

[ Readonly<T> ]

type User1 = {
    id: number; 
    name: string;
}
const user1: Readonly<User1> = {
    id: 1, 
    name: "John"
}

// Readonly 이기 때문에 변경 불가로 오류 발생
user1.id = 10
user1.name = "Amy"
  • User의 속성을 모두 Readonly로 바꿔준다
  • user1 에 마우스 호버 시 모든 속성 앞에 readonly 붙음

[ Pick<T, K> ]

type User2 = {
    id: number; 
    name: string; 
    email: string;
}

type UserWithNameOnly = Pick<User2, "name">;

// name 값만 선택하여 생성
const User2: UserWithNameOnly = {
    name: "lee"
}

[ Omit<T, K> ]

type Product = {
    id: number; 
    name: string; 
    price: number;
    uniqueCode: number;
}

// 아래 예제는 여러 개 선택하였지만 하나도 가능 !!
type ProductWithOmit = Omit<Product, 'uniqueCode' | 'price'>;
  • Omit 을 통하여 포함시킨 속성들을 [ 제외 ] 한 속성들만 생성
  • 'uniqueCode' | 'price' 제외 후 id , name 만 생성

[ Record<K, T> ]

// [1] 키가 모두 같은 타입
type Country = "South Korea" | "United States" | "Canada";
type Capital = string;

// Country 값이 키값이고, Capital 값 즉 내가 쓰는 string 타입의 값이 키에대한 값이 된다
type CountryCapitals = Record<Country, Capital>;

const capitals: CountryCapitals = {
    "South Korea": "Seoul",
    "United States": "Washington D.C",
    "Canada" : "Ottawa"
}

// [2] 키 값에 대한 타입이 개별적으로 상이할 때 

type CountryInfo = {
    capital: string;
    population: number; 
    continent: string;
}

type CountryInfoMap = Record<Country, CountryInfo>

const countryInfo: CountryInfoMap = {
    'South Korea': {
        capital: 'Seoul', 
        population: 51_000_000, 
        continent: 'Asia'
    },
    'United States': {
        capital: 'Washington D.C', 
        population: 331_000_000, 
        continent: 'North America'
    },
    'Canada' : {
        capital: 'Otat=wa', 
        population: 83_000_000, 
        continent: 'North America'
    }
}

[ Parameters<T> ]

type SaveUser = (name: string, age: number) => void;

type Params = Parameters<SaveUser>

function saveUser(... params: Params) {
    const [ name, age ] = params;
}

saveUser ("David", 33)

상수 단언(const assertion)

• 확장할 수 없는 리터럴 타입의 변수를 생성
• 애플리케이션 전역에 걸쳐 올바른 값 사용을 보장
• 컴파일러의 정확한 타입 추론을 돕는다

const someObject = { } as const

📍 예제

// [객체]
const book = {
    title: "TypeScript Guide", 
    author: "coding moon"
} as const;

// 오류 발생
book.title = "another title";

// [배열]
const nums = [1,2,3,4,5] as const;

// 오류 발생
nums.push(6)

// [실전 예제 1]
const config = {
    server: 'https://api.somedomain.com',
    port: 8080, version: 2
} as const;

// 오류 발생
config.server = "https: "

// [실전 예제 2] mapping 객체 사용

// 주문 시스템의 상태정보
export const statusCodeMap = {
    101: "ordered",
    102: "pending",
    103: "completed"
} as const;

export type statusCodeKeys = keyof typeof statusCodeMap;

function handleStatus (statusCode: statusCodeKeys) {
     //             statusCodeMap[102] // 👉 "pending" 값을 가져온다 
    const message = statusCodeMap[statusCode];
    // UI 업데이트 또는 로직처리
}

0개의 댓글