본문 바로가기

IT

Effective Typescript - 43 - 몽키 패치보다는 안전한 타입을 사용하기

저자는 자바스크립트의 가장 유명한 특징을 객체나 클래스에 임의의 속성을 임의로 추가할 수 있을 만큼 유연하다는 것을 꼽습니다. 자바스크립트 FE 개발 시, window나 document 객체에 임의의 속성을 만들어 값을 할당하여 전역 변수처럼 사용하곤 합니다. 이런 행위를 몽키 패치라고 표현합니다.

window.monkey = 'test';
document.monkey = 'monky patch';

const el = document.getElementById('monky-div');
el.monkey = 'cached!';

사실 객체에 위와 같이 몽키패치 하듯 임의의 속성을 추가하는 것은 일반적으로 좋은 설계가 아니라고 합니다.

 

타입스크립트 관점에서 몽키 패치를 사용한다면, 예를 들어 document 또는 window에 임의의 속성을 추가하면 문제가 발생합니다.

window.monkey = 'test'; // 에러) Property 'monky' does not exist on type 'Window & typeof globalThis'.(2339)
document.monkey = 'monky patch'; // 에러) Property 'monky' does not exist on type 'Window & typeof globalThis'.(2339)

//위 오류를 해결하기 위한 work around
(window as any).monkey = 'test'; // 우선 에러는 안나옴
(document as any).monkey = 'monky patch'; // 우선 에러는 안나옴

위 예제에서 몽키 패치를 위해 any를 사용했는데 이는 계속 학습했듯, side effect의 위험을 높입니다.

따라서 다른 방법을 생각해보면 이어질 내용과 같은 방법을 쓸 수 있습니다.

 

첫 번째로, interface 보강 기능을 사용하는 겁니다.

interface Document {
    monkey : string;
} // 인터페이스 보강

document.monkey = 'monky patch with interface augmentation' // 정상

보강을 사용하는 것이 any로 분리하는 방법보다 더 낫습니다. 이유는 타입이 더 안전하기 때문입니다. 이 밖에 타입스크립트 언어의 기능을 그대로 받을 수 있다는 점도 있습니다.

 

그리고 모듈의 관점에서(import / export 사용 시), 제대로 동작하게 하려면 global 선언을 추가해야 합니다.

export {};
declare global {
    interface Document {
        /** 몽키 패치 */
        monkey: string
    }
}

document.monkey = 'test';

보강을 사용할 때 주의해야 할 점은 모듈 영역(scope)과 관련이 있습니다. 보강은 전역적으로 적용이 되기 때문에, 코드의 다른 부분이나 라이브러리로부터 분리할 수 없습니다. 

 

두 번째로, 더 구체적인 타입 단언문을 사용하는 것입니다.

interface MonkeyDocument extends Document {
    monkey: string;
}
(document as MonkeyDocument).monkey = 'monkey path test';

마찬 가지로 이와 같은 방법도 타입스크립트 언어 기능을 모두 지원받을 수 있습니다. any로 변환해서 사용하는 것보다 훨씬 안전한 방법입니다.

 

[요약]

* 전역 변수나 DOM에 데이터를 저장하지 말고, 데이터를 분리하여 사용해야 합니다.

* 내장 타입 데이터를 저장해야 하는 경우, 안전한 타입 접근법(인터페이스 보강, 사용자 정의 확장 인터페이스로 단언)을 활용해야 합니다.

* 보강의 모듈 영역 문제에 대해 이해하고 있어야 합니다.