저는 처음 타입스크립트를 접할 때 단순히 자바스크립트의 불안정성을 타입 체크를 통해 보완해주는 슈퍼셋 정도로만 생각했습니다.
하지만 책을 읽으면서 타입스크립트만의 특이한 특징들을 알게 되었고, 유명한 특징들을 기반으로 넘겨 짚었지만 틀린 사실도 있었기에 이 부분들을 짚고 넘어가고자 합니다 🙂
자바스크립트는 현재 멀티코어 병렬 프로그래밍 환경에 적합한 매커니즘으로 인식되고 있습니다. :)
아래는 프로그래밍 언어의 인기도 측정 지표인 PYPL의 2024년도 표로 JavaScript가 3위인걸 보면 이용자 수가 많다는 걸 알 수 있습니다.
자바스크립트만의 독보적인 생산성과 유연성은 두드러지는 장점이지만 그만큼 안전성과 신뢰성이 떨어진다는 단점이 있습니다.
이를 보완해줄 수 있는 것이 자바스크립트의 슈퍼셋, 타입스크립트입니다.
타입스크립트를 통해 자바스크립트 개발자들이 가장 골치아파하던 타입 불안전성이 극복되면서
자바스크립트가 보다 효율적일 수 있도록 완성도를 높여주었습니다.
타입스크립트는 자바스크립트의 상위 집합(슈퍼셋)이기 때문에 자바스크립트 프로그램에 문법 오류가 없다면 유효한 타입스크립트 프로그램이라고 간주할 수 있습니다. 이러한 특성은 기존의 자바스크립트 코드를 타입스크립트로 마이그레이션(migration)하는 과정이 복잡하지 않다는 장점을 가집니다.
위 벤다이어그램을 보면 알 수 있듯이, 자바스크립트와 공유되지 않는 타입스크립트만의 고유한 특성이 존재합니다.
이는 타입스크립트가 타입을 명시하는 추가적인 문법을 갖기 때문입니다.
따라서 모든 자바스크립트는 타입스크립트이지만, 모든 타입스크립트가 자바스크립트가 될 순 없습니다.
더 정확히 말하면 타입스크립트도 모든 자바스크립트를 포함하진 않습니다.
'타입스크립트는 자바스크립트의 상위 집합이라며..?'
모든 타입스크립트가 자바스크립트를 포함하지 않는 이유는 타입 체크 영역과 관련이 있습니다.
타입스크립트의 타입 시스템은 자바스크립트의 런타임 동작을 모델링 합니다.
따라서 런타임 체크를 엄격하게 하는 언어를 사용해 왔다면 타입스크립트의 허술함에 당황할 수 있습니다.
타입 체커를 사용하게 되면 오류가 나야할 것 같은 코드를 정상으로 인식하는 경우도 있고, 정상 동작하는 코드에 오류를 표시하기도 합니다.
이러한 경우들은 단순히 런타임 동작을 모델링하는 것 뿐 아니라 의도치 않은 이상한 코드가 오류로 이러질 수 있다는 점까지 고려해야 합니다.
타입스크립트의 도움을 받으면 오류가 적은 코드를 작성할 수 있는 것이지 모든 오류에 대응할 수 있는 것이 아님을 명심해야 합니다.
위와 같은 오류들이 발생하는 근본적인 원인은 타입스크립트가 이해하는 값의 타입과 실제 값에 차이가 있기 때문입니다.
애초에 타입 시스템은 정적 타입의 정확성을 보장해주기 위해서 만들어지지 않았습니다.
정확성을 보장해야 한다면 Reason이나 Elm 같은 다른 언어를 선택하는 것이 좋습니다.
1. 개발 문서화
2. 생산성 (자동완성)
3. 안정적인 개발 환경 (컴파일 단계에서의 에러 검출)
"확장 가능한 자바스크립트"
확장의 중요한 부분이 바로 타입스크립트 경험의 핵심 요소인 언어 서비스입니다.
타입스크립트의 언어 서비스를 제대로 누려야지만 개발자들의 생산성이 향상될 수 있기 때문입니다.
프로그래밍 실수를 실시간으로 잡아냄으로써 일반적인 오류를 최소화하는 동시에 실행 속도 절감 효과
타입을 사전에 명시한다는 점에서 결과에 대한 안정성과 신뢰성을 확보
자바스크립트의 슈퍼셋, 쉽고 빠른 전환을 위해 자바스크립트의 런타임 특성을 변화시키지 않아 TS 관련 프레임워크 공부 X
에러 예방
타입스크립트의 타입 체크를 통해 코드 작성시에 사전 약속에 부합하지 않은 타입을 넣거나 혹은 넣어야 하는 타입을 넣지 않았을 경우 사전에 ID에서 오류라고 경고를 줍니다. 컴파일 시점에 오류를 미리 알 수 있으므로 추후 디버깅하는 시간을 줄여준다는 점에서 생산성이 높아집니다.
실행 속도
자바스크립트는 동적 타입의 인터프리티 언어입니다. 즉, 런타임(실행)시 타입을 결정해서 적용합니다.
이런 로직은 '컴퓨터'에게 오류가 있는지 없는지 체크하라고 일을 맡겨버린 것과 같기 때문에 실행 속도가 당연히 오래 걸릴 수 밖에 없습니다.
하지만 타입스크립트는 '사람'이 코드작성 시에 타입을 미리 결정하고 오류를 수정할 수 있기 때문에 기계가 미리 기억을 할 필요가 없어집니다. 이에 따라 자바스크립트에 비해 실행 속도가 매우 빠르다는 장점이 있습니다.
안정성 & 협업용이성
타입스크립트는 타입을 명시할 수 있고 컴파일 시 오류를 찾기 때문에 보다 더 안정적이라고 말할 수 있습니다.
JS DOC의 역할도 상당 부분 대체할 수 있을 뿐만 아니라 자동 완성 등 언어 서비스를 제공해 주기 때문에
협업 시에도 자바스크립트에 비해 매우 용이하다고 볼 수 있습니다.
슈퍼셋 (superset)
타입스크립트는 자바스크립트의 슈퍼셋, 즉 자바크립트의 +알파입니다.
따라서 자바스크립트와 100% 호환이 되는건 물론이며, 이 외에 클래스,인터페이스 등 객체지향 프로그래밍 패턴을 제공합니다.
자바스크립트의 단점은 줄여주고 나아가 더 좋은 기능들을 감싼 형태라고 이해할 수 있습니다.
지원
IDE와 같은 도구에 타입 정보를 제공함으로써 높은 수준의 인텔리센스(IntelliSense), 코드 어시스트, 타입 체크, 리팩토링 등을 지원받을 수 있습니다. 이는 대규모 프로젝트에서 필수적인 기능으로 대규모 프로젝트에서 또한 유리하다는 장점이 있습니다.
JS를 잘 알고 있다 하더라도 새로운 문법 및 개념을 익히는데 시간이 상당 소요, 높은 학습 곡선
대규모 프로젝트에서는 컴파일 시간이 더욱 길어짐과 동시에 더 많은 리소스를 요구
타입 오류의 발생 가능성 및 라이브러리 호환에 문제 가능성 존재
초기의 학습 부담 증가 (긴 러닝커브)
타입스크립트는 자바스크립트에 비해 학습해야할 추가적인 개념들이 많습니다.
타입의 종류 뿐만 아니라 정적 타이핑, 인터페이스, 제네릭 등의 새로운 개념을 학습할 필요가 있습니다.
개발 시간 증가
타입스크립트를 사용하면 정적 타입을 선언하고 관리해야하기 때문에 코드 작성과 유지 보수에 드는 시간이 길어질 수 있습니다.
코드의 길이도 타입을 부여하다보면 길어지기 때문에 가독성이 떨어져 이해하는데 시간이 걸릴 수 있습니다.
라이브러리 호환성 문제
모든 자바스크립트 라이브러리가 타입스크립트와 완벽하게 호환되지는 않습니다.
이러한 경우에 추가적인 작업이 필요할 수 있으며, 이는 개발 과정을 복잡하게 만들 수 있습니다.
타입 오류의 발생 가능성
정적 타입 시스템을 사용하기 때문에 변수나 함수 등의 타입이 맞지 않을 경우 오류가 발생합니다.
하지만 모든 오류를 찾아내기 어렵기 때문에 타입 오류가 발생할 가능성이 있다는 점을 염두에 두어야 합니다.
더 많은 리소스 요구
타입스크립트는 자바스크립트에 비해 더 많은 시스템 리소스를 사용할 수 있습니다.
특히, 큰 프로젝트에서는 메모리 사용량과 처리 시간이 증가할 수 있습니다.
긴 컴파일 시간
자바스크립트로 변환되기 전에 컴파일 과정을 거쳐야 합니다. 이로 인해 개발 중 컴파일 시간이 길어질 수 있으며,
특히 대규모 프로젝트에서는 컴파일 시간이 더욱 길어질 수 있습니다.
개발자와 프로젝트 각각의 특성을 고려해 시의적절하게 사용하는 것이 가장 중요합니다 :)
대규모 프로젝트일 때
두 명 이상 개발자가 개발하거나, 코드 양이 방대할 때
타입 체크를 통해 사전에 오류를 발견해 고칠 수 있고 제공되는 언어 서비스, 코드 어시스트 등 지원을 받을 수 있기 때문에
타입스크립트의 사용이 유리합니다.
협업 시 정적 타입 언어가 편리한 개발자가 많을 때
C#, Java 언어에 익숙한 개발자가 많을 때에는 자바스크립트의 자유롭고 유연한 특성이 불편할 수가 있습니다.
타입스크립트는 C#을 만든 사람이 개발하였기 때문에 구문이 비슷하고 쉽게 적응 가능하기 때문에
이러한 상황에선 타입스크립트 사용이 유리할 수 있습니다.
구형 브라우저에서도 작동이 필요할 때
타입스크립트를 이용하여 컴파일을 하게 되면 IE에서도 작동할 수 있도록 변환이 가능합니다.
Babel의 역할을 대신할 수 있으므로 구형 브라우저에서도 작동해야 한다면 타입스크립트를 이용하는 것이 유리합니다.
1. 최신 TS/JS가 브라우저에서 동작할 수 있도록 구버전의 JS로 transfile
2. 코드의 타입 오류를 체크
타입스크립트 컴파일러는 크게 두 가지 기능을 수행하는데 서로 완전히 독립적이라는 특징을 갖고 있습니다.
타입스크립트가 자바스크립트로 변환될 때 코드 내의 타입에는 영향을 주지 않고 실행 시점에도 타입은 영향을 미치지 않습니다.
독립적으로 각각의 기능을 수행한다는 것은 아래 상황들을 살펴보면 더욱 이해가 잘 됩니다.
타입 오류가 있는 코드도 컴파일이 가능합니다.
문제가 될 수 있는 부분들을 알려주지만 빌드를 멈추지는 않습니다.
let mobi = 'developer community';
mobi = 2024; // '2024' 형식은 'string' 형식에 할당할 수 없습니다.
이렇게 보면 타입스크립트에 허점이 있는 것 같지만 사실 코드에 오류가 있더라도 컴파일된 산출물이 나오는 것이 도움이 됩니다.
웹 애플리케이션을 만드는 경우, 오류를 수정하지 않더라도 애플리케이션의 다른 부분 테스트가 가능하기 때문입니다.
만약 오류가 있을 때 컴파일을 허용하고 싶지 않은 경우 noEmitOnError를 true로 설정해주면 됩니다.
타입스크립트의 타입은 '제거 가능(erasable)' 합니다.
실제로 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 제거돼 버립니다.
// sample code
interface Member {
name: string,
age: number,
developer: string,
isMember: boolean,
term: number
}
interface Operator extends Member {
isOperator : boolean,
isDirector : boolean
}
type Mobi = Member | Operator;
function isDirector(member: Mobi){
if(member instanceof Operator){
// .... codes
return member.isDirector; // 👉 오류: 'Operator'형식에 'isDirector' 속성이 없습니다.
} else {
return member.term;
}
}
타입을 명확히 하기 위해서는 런타임에 타입 정보를 유지하는 방법이 필요합니다.
런타임에 타입을 유지하는 방법엔 세 가지 정도가 있습니다 :
속성 체크는 런타임에 접근 가능한 값에만 관련됩니다.
function isDirector(member: Mobi){
if(isDirector in member){ // 👉 속성 존재 여부 체크
member; // type = Operator
return member.isDirector;
} else {
return member.term;
}
}
태그 기법은 런타임에 타입 정보를 손쉽게 유지할 수 있어 흔히 볼 수 있는 방법입니다.
interface Amy {
name: 'Amy',
age: 20,
developer: 'frontend',
isMember: true,
term: 2
isOperator: false,
}
interface Peanut {
name: 'Peter',
age: 20,
developer: 'full',
isMember: true,
term: 0,
isOperator : true,
isDirector : true
}
type Mobi = Amy | Peanut;
function isDirector(member: Mobi){
if(member.isDirector === 'isDirector'){ // 👉 tagged union
member; // type = Peanut
return member.isDirector;
} else {
member; // type = Member
return member.term;
}
}
interface는 타입으로만 사용 가능하지만, Member나 Operator를 class로 선언하게 되면
타입과 값으로 모두 사용할 수 있으므로 오류가 발생하지 않습니다.
class Member {
constructor(public isMember: boolean){}
}
class Operator extends Member {
constructor(pulic isMember: boolean, public isOperator: boolean){
super(isMember);
}
}
type Mobi = Member | Operator; // 👉 타입으로 참조되는 부분
function isOperator(member: Mobi){
if(member instanceof Operator){ // 👉 값으로 참조되는 부분
member; // type = Operator
return member.isOperator;
} else {
member; // type = Member
return member.isMember;
}
}
string 또는 number 타입인 값을 항상 number로 정제하는 경우, 아래는 잘못된 방법의 예 입니다.
function OnlyNumber(value: number | string):number {
return value as number;
}
위 함수가 정제됐을 때의 모습은 아래와 같습니다 :
function OnlyNumber(value){
return value;
}
코드에 정제 과정이 없습니다.
값을 정제하기 위해서는 런타임의 타입을 체크해야 하고 자바스크립트 연산을 통해 변환을 수행해야 합니다.
function OnlyNumber(value: string | number):number {
return typeof(value) === 'string' ? Number(value) : value;
}
타입스크립트에서는 런타임 타입과 선언된 타입이 맞지 않을 수 있습니다.
선언된 타입이 언제든지 달라질 수 있다는 것을 명심해야 합니다.
동일한 이름에 매개변수만 다른 여러 버전의 함수를 '함수 오버로딩'이라고 합니다.
타입스크립트에서는 타입과 런타임의 동작이 무관하기 때문에 함수 오버로딩이 불가능합니다.
함수 오버로딩 기능을 지원하지만, 온전히 타입 수준에서만 가능합니다.
하나의 함수에 대해 여러 개의 선언문을 작성할 순 있지만, 구현체(implementation)는 오직 하나뿐입니다.
타입과 타입 연산자는 자바스크립트 변환 시점에 제거되기 때문에 런타임의 성능에 영향을 주지 않습니다.
타입스크립트를 사용하면서 주로 자주 추가하는 설정에는 무엇이 있을까요?
1. noImplicitAny : true ⭐️
2. strictNullChecks : true
noImplicitAny는 변수들이 미리 정의된 타입을 가져야 하는지에 대한 여부를 제어하는 설정입니다.
타입을 사전에 선언하지 않으면 any 타입이 적용되기 때문에 이를 방지하는 겁니다.
any 타입의 사용은 타입스크립트를 사용하는 이유를 무력화하기 때문에 사용 자제를 권장합니다.
noImplicitAny의 설정은 타입 명시를 강제하기 때문에
문제 발견을 수월히 할 수 있고 코드의 가독성이 좋아져 결과적으로 개발자의 생산성이 향상될 수 있습니다.
noImplicitAny의 설정 해제는 자바스크립트로 돼 있는 기존 프로젝트를 타입스크립트로 전환할 때 뿐입니다.
strictNullChecks는 null과 undefined가 모든 타입에서 허용되는지를 확인하는 설정입니다.
strictNullChecks를 true로 설정하게 되면 " undefined는 객체가 아닙니다 "와 같은 오류를 피할 수 있습니다.
하지만 이 설정은 코드의 작성을 어렵게 하기 때문에 사용하기로 결정했다면 초반에 설정해두는 것이 좋습니다.
이 외에도 많은 설정들을 전부 true로 하고 싶다면 strict 설정을 고려해보는 것이 좋습니다 :)
strict를 설정하게 될 경우 대부분의 오류를 사전에 확인할 수 있습니다.
타입스크립트의 설정에는 세 가지 방법으로 접근할 수 있습니다.
커맨드 라인에 직접 입력
$ tsc --noImplicitAny program.ts
tsconfig.json 설정 파일을 통해 입력 ⭐️
$ tsc --init // tsconfig.json 파일 생성
{
"compilerOptions": {
"noImplicitAny" : true
}
}
타입스크립트의 엄격한 체크를 하고 싶다면 strict 설정
덕 타이핑이란 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우, 객체를 해당 타입에 속하는 것으로 간주하는 방식입니다.
자바스크립트는 본질적으로 덕 타이핑 기반이고, 타입스크립트는 이런 동작을 그대로 모델링합니다.
구조적 타이핑을 제대로 이해하면 더욱 견고한 코드를 작성할 수 있습니다.
함수를 작성할 때 호출에 사용되는 매개변수의 속성들이 매배견수의 타입에 선언된 속성만을 가질 거라 생각하기 쉽습니다.
이러한 타입을 '봉인된(sealed)' / '정확한(percise)' 타입이라고 부릅니다.
타입스크립트 타입 시스템에서는 위와 같은 타입을 표현할 수 없습니다.
타입스크립트는 '열린(open)' 타입이며 이러한 특성 때문에 당황스러운 결과가 발생하기도 합니다.
구조적 타이핑은 테스트를 할 때나 라이브러리 간의 의존성을 분리할 때 두 가지 정도 경우에 장점을 발휘할 수 있습니다.