Blog

이펙티브 타입스크립트(1)

이펙티브 타입스크립트(1)

1-1. 타입스크립트와 자바스크립트의 관계 이해하기

타입스크립트는 자브스키릅트의 상위집합 (superset)이다 타입스크립트는 타입이 정의된 자바스크립트의 상위집합이다
모든 자바스크립트 프로그램이 타입스크립트라는 명제는 참이지만, 반대는 성립하지 않는다
타입스크립트 컴파일러는 타입스크립트뿐만 아니라 일반 자바스크립트 프로그램에도 유용하다
타입스크립트의 목표 중 하나는 런타임에 오류를 발생시킬 코드를 미리 찾아내는 것이다
타입스크립트가 정적 타입 시스템이라는 것은 바로 이런 특징을 말하는것이다
타입 체커가 모든 오류를 찾아내지는 않는다

요약

타입스크립트는 자바스크립트 런타임 동작을 모딜렝하는 타입 시스템을 가지고 있기 때문에 런타임 오류를 발생시키는 코드를 찾아내려고 한다
하지만, 타입 체커를 통과하면서도 런타임 오류를 발생시키는 코드는 충분히 존재할 수 있다
타입스크립트 타입 시스템은 전반적으로 자바스크립트 동작을 모델링한다
잘못된 매개변수 개수로 함수를 호출하는 경우처럼, 자바스크립트에서 허용되지만 타입스크립트에서는 문제가 되는 경우도 있다
문격의 엄격함은 온전히 취향의 차이이며, 우열을 가릴 수 없는 문제이다

1-2. 타입스크립트 설정 이해하기

타입스크립트 설정은 현재 시점으로 거의 100개에 이르는데, 가급적 tsconfig.json 파일로 설정을 관리하는것이 좋다
noImplicitAny 는 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어한다
타입스크립트는 타입 정보를 가질 때 가장 효과적이기 때문에, 가급적 noImplicitAny 를 설정해야 한다
strictNullChecksnullundefined 가 모든 타입에서 허용되는지 확인한다
strictNullChecksnullundefined 관련된 오류를 잡아 내는데 많은 도움이 되지만, 코드 작성을 어렵게 만든다
새 프로젝트를 시작한다면 가급적 설정하는것이 좋지만, 타입스크립트가 처음이거나 자바스크립트 코드를 마이그레이션하는 중이라면 설정하지 않아도 괜찮다
any 타입을 매개변수에 사용하면 타입 체커는 속절없이 무력해진다
any 타입은 유용하지만 매우 주의해서 사용해야 한다

1-3. 코드 생성과 타입이 관계없음을 이해하기

타입스크립트 컴파일러가 하는 역할
최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일(transpile) 한다
코드의 타입 오류를 체크한다
타입스크립트 컴파일러가 하는 역할은 완벽히 독립적으로 수행된다

타입 오류가 있는 코드도 컴파일이 가능하다

코드에 오류가 있을 때 컴파일에 문제가 있다 가 있지만, 이는 기술적으로 틀린말이다. → 작성한 타입스크립트가 유효한 자바스크립트라면 타입스크립트 컴파일러는 컴파일을 해낸다. → 코드에 오류가 있을 때 타입 체크에 문제가 있다 라고 말하는게 더 정확한 표현이다
컴파일은 타입 체크와 독립적으로 동작하기 때문에, 타입 오류가 있는 코드도 컴파일이 가능하다
타입 오류가 있는 데도 컴파일된다는 사실 때문에 타입스크립트가 엉성한 언어처럼 보일 수 있지만, 실제로 산출물이 나는 것이 실제로 도움이 될 때가 있다
만약 오류가 있을 때 컴파일하지 않으려면, noEmitOnError 를 설정하거나 빌드 도구에 동일학게 적용하면 된다

런타임에는 타입 체크가 불가능하다

타입스크립트의 타입은 제거 가능(erasable) 하다.
자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 제거된다
타입 정보를 유지하는 방법으로는 런타임에 접근 간으한 타입 정보를 명시적으로 저장하는 태그 기법이 있다
예시 코드
위 예시코드에서 Shape태그된 유니온(tagged union) 의 예시이다
이 기법은 런타임에 타입 정보를 손쉽게 유지할 수 있기 때문에, 타입스크립트에서 흔하게 볼 수 있는 기법이다
타입 (런타임 접근 불가) 와 값 (런타임 접근 가능) 을 둘 다 사용하려면 타입을 클래스로 만들면 된다
예시 코드
인터페이스는 타입으로만 사용 가능하지만, 클래스로 선언하면 타입과 값으로 모두 사용할 수 있다

타입 연산은 런타임에 영향을 주지 않는다

as {type} 은 타입 단언문이다.
잘못된 예시
타입스크립트
function asNumber(val: number | string): number { return val as number; }
TypeScript
자바스크립트
function asNumber(val) { return val }
TypeScript
위의 잘못된 예시처럼 타입 체커는 통과하지만, 실제로 컴파일 해보면 런타임 동작에는 아무런 영향을 미치지 않는다.
값을 정제하기 위해서는 런타임의 타입을 체크해야 하고, 자바스크립트 연산을 통해 변환을 수행해야 한다

런타임 타입은 선언된 타입과 다를 수 있다

타입스크립트에서는 런타임 타입과 선언된 타입이 맞지 않을 수 있다
타입이 달라지는 혼란스러운 상황을 가능한 피해야 한다.
선언된 타입이 언제든지 달라질 수 있다는 것을 명심해야 한다.
예시 코드

타입스크립트 타입으로 함수를 오버로드 할 수 없다

함수 오버로딩이란? → 동일한 이름에 매개변수만으로 다른 여러 버전의 함수를 사용하는것을 말한다.
타입스크립트에서는 타입과 런타임의 동작이 무관하기 때문에, 함수 오버로딩은 불가능하다
타입스크립트가 함수 오버로딩 기능을 지원하기는 하지만, 온전히 타입 수준에서만 동작한다
하나의 함수에 대해 여러 개의 선언문을 작성할 수 있지만, 구현체(implementation) 은 오직 하나 뿐이다
타입스크립트에서는 타입 정보를 제공할 뿐이며, 실제 컴파일되면서 제거되고 구현체만 남게된다
예시 코드

타입스크립트 타입은 런타임 성능에 영향을 주지 않는다

타입과 타입 연산자는 컴파일 시점에 제거되기 때문에, 런타임의 성능에 아무런 영향을 주지 않는다
런타임 오버헤드가 없는 대신, 타입스크립트 컴파일러는 빌드타임 오버헤드가 있다
타입스크립트 팀은 컴파일러 성능을 매우 중요하게 생각한다
컴파일은 일반적으로 상당히 빠른 편이며, 증분(incremental) 빌드 시 더욱 체감된다
오버헤드가 커지면, 빌드 도구에서 트랜스파일만 하는 옵션(transpile only)을 설정하여 타입 체크를 건너뛸 수 있다
타입스크립트가 컴파일하는 코드는 오래된 런타임 환경을 지원하기 위해 호환성을 높이고 성능 오버헤드를 감안할지, 호환성을 포기하고 성능 중심의 네이티브 구현체를 선택할 지의 문제에 맞닥뜨릴 수도 있다
제네레이터 함수가 ES5 타깃으로 컴파일되면, 타입스크립트 컴파일러는 호환성을 위해 특정 헬퍼 코드를 추가한다
제네레이터의 호환성을 위한 오버헤드 또는 성능을 위한 네이티브 구현체의 선택의 문제이다
호환성과 성능 사이의 선택은 컴파일 타깃과 언어 레벨의 문제이며, 타입과 무관하다

1-4. 구조적 타이핑에 익숙해지기

덕 타이핑이란? → 객체가 어떤 타입에 부합하는 변수와 메서드를 갖리 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식이다
자바스크립트는 본질적으로 덕 타이핑(duck typing) 기반이다.
타입스크립트는 매개변수 값이 요구사항을 만족한다면 타입이 무엇인지 신경쓰지 않는 동작을 그대로 모델링한다.
예시 코드
구조적 타이핑 (structural typing) 때문에 문제가 발생할 수 있다
예시 코드
함수를 작성할 때, 호출에 사용되는 매개변수의 속성들이 매개변수의 타입에 선언된 속성만을 가질 거라 생각하기 쉽다.
이러한 타입은 봉인된 (sealed) 또는 정확한 (precise) 타입이라고 불리며, 타입스크립트 타입 시스템에서는 표현할 수 없다
타입스크립트 타입은 open 상태이다
open 은 타입 확장에 열려있다는 의미이다
타입에 선언된 속성 외에 임의의 속성을 추가하더라도 오류가 발생하지 않는다는것을 의미한다
예시 코드
구조적 타이핑은 클래스와 관련된 할당문에서도 당황스러운 결과를 보여준다
예시 코드

정리

자바스크립트가 덕 타이핑 기반이고, 타입스크립트가 이를 모델링하기 위해 구조적 타이핑을 사용한다
어떤 인터페이스에 할당 가능한 값이라면 타입 선언에 명시적으로 나열된 속성들을 가지고 있다
타입은 ‘봉인'되어 있지 않다
클래스 역시 구조적 타이핑 규칙을 따른다
클래스의 인스턴스가 예상과 다를 수 있다
구조적 타이핑을 사용하면 유닛 테스팅을 쉽게 할 수 있다

1-5. any 타입 지양하기

타입스크립트의 타입 시스템은 점진적(gradual) 이고 선택적(optional) 이다
일부 특별한 경우를 제외하고는 any 를 사용하면 타입스크립트의 장점을 누릴 수 없다
any 타입에는 타입 안정성이 없다
any 는 함수 시그니처를 무시한다
자바스크립트에서는 종종 암시적으로 타입이 변환되기 때문에 문제가 발생할 수 있다
예시 코드
any 타입에는 IntelliSense 가 동작하지 않는다
타입스크립트의 모토는 확장 가능한 자바스크립트 이다
any 를 사용하게 되면 언어 서비스를 제대로 누릴 수 없다
any 타입은 코드 리팩토링 때 버그를 감춘다
예시 코드
any 는 타입 설계를 감춘다
애플리케이션 상태 같은 객체를 정의하려면 꽤 복잡하지만 any 를 사용하면 간단하게 해결해버릴 수 있다.
하지만, any 를 사욯아게 되면 상태 객체의 설계 자체를 감춰버리기 때문에 문제가 발생할 숭 ㅣㅅ다.
any 를 사용하면 타입 설계가 불분명해진다
any 는 타입시스템의 신뢰도를 떨어뜨린다