Dev/etc.
이펙티브 타입스크립트 아이템 1 ~ 10
takeU
2023. 6. 1. 10:10
반응형
아이템 1 - 타입스크립트와 자바스크립트의 관계 이해하기
타입스크립트는 자바스크립트의 상위집합(superset)
'정적' 타입 시스템 - 컴파일 시 변수의 타입을 지정하는 것
타입스크립트의 특징
- 자바스크립트의 런타임 동작을 모델링 함
- 타입 시스템은 런타임에 오류를 발생시킬 코드를 미리 찾아 냄
- 모든 오류를 찾아주지는 않고, 의도와 다르게 동작할 수도 있음
- 타입 체커를 통과하면서도 런타임 오류를 발생시킬 수 있음
아이템 2 - 타입스크립트 설정 이해하기
tsconfig.json
에서 설정tsc --init
으로 설정파일 생성
대표적인 설정값
noImplicitAny
- 변수들이 미리 정의된 타입을 가져야 하는지 여부 제어, Any도 불가능.
- 타입을 정하지 않았어도 암시적으로 any로 간주하여 오류 발생시킴.
- 되도록 해당 설정 해야함.
strictNullCheckes
null
과undefined
가 모든 타입에서 허용되는지 여부null
을 쓰려고 하면 유니온 타입으로 null 명시.- 오류 잡는데 많은 도움이 되지만 타입스크립트가 처음이거나 마이그레이션한다면 설정하지 않아도 괜찮음.
strict
로 엄격한 체크 가능
아이템 3 - 코드 생성과 타입이 관계없음을 이해하기
TS 컴파일러의 역할
- 최신 TS/JS를 구버전 JS로 트랜스파일
- 타입 오류 체크
이 때 두가지는 독립적으로 작동하기 때문에 다음과 같은 경우가 생길 수 있다.
타입 오류가 있어도 컴파일이 가능
- 경고를 해주지만 빌드를 멈추지는 않음
- 즉, 작성한 TS가 유효한 JS라면 컴파일을 정상적으로 실행
- 오류가 있을 때 컴파일을 멈추려면
noEmitOnError
속성을 추가
런타임에는 타입 체크가 불가능
- 컴파일되는 과정에서
interface
,type
, 타입 구문은 제거됨
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
// ~~~~~~~~~ 'Rectangle' only refers to a type,
// but is being used as a value here
// 런타임 시점에서 타입이 모두 지워져서 Rectangle이 값으로 여기게 됨
return shape.width * shape.height;
// ~~~~~~ Property 'height' does not exist
// on type 'Shape'
} else {
return shape.width * shape.width;
}
}
- 따라서 타입을 명확하게 하기 위해선 세 가지 방법이 존재함
- 타입 가드 - 해당 속성이 존재하는지 확인하는 방법으로 구분(
if (type in class)
) - 태그 기법 -
type Shape = Square | Rectangle
과 같이 Union으로 엮어 구분 - 타입을 클래스로 만들어
interfaceof
로 구분
- 타입 가드 - 해당 속성이 존재하는지 확인하는 방법으로 구분(
class Square {
constructor(public width: number) {}
}
class Rectangle extends Square {
constructor(public width: number, public height: number) {
super(width);
}
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
shape; // Type is Rectangle
return shape.width * shape.height;
} else {
shape; // Type is Square
return shape.width * shape.width; // OK
}
}
타입 연산은 런타임에 영향을 주지 않음
function asNumber(val: number | string): number {
return val as number;
}
- as number를 사용해서 타입을 바꾸는 방식은 아무런 영향이 없게 작동됨,
- as number는 타입연산이고 런타임에서는 동적하지 않음.
function asNumber(val: number | string): number {
return typeof(val) === 'string' ? Number(val) : val;
}
- 런타임에서 타입 체크 및 연산을 하기 위해서는 위와 같이 수행 해야함.
런타임 타입은 선언된 타입과 다를 수 있음
- 타입선언문이 런타임에 제거되기 떄문에 여러 코드가 실행되면서 정확한 타입을 보장해주지는 않음.
타입으로는 함수를 오버로드 할 수 없음
- 타입과 런타임 동작이 무관하기 때문에, 함수 오버로딩이 불가능
- 같은 이름의 다른 두 개의 선언문은 JS로 변환되면서 제거되고, 구현체만 남게됨
타입은 런타임 성능에 영향을 주지 않음
- 타입과 타입 연산자는 JS 변환 시점에 제거되기 때문에, 런타임 성능에 영향을 주지 않음
- 즉, 정적 타입은 비용이 들지 않음
- 런타임 오버헤드 대신 빌드타임 오버헤드 존재
아이템 4 - 구조적 타이핑에 익숙해지기
구조적 타이핑이란?
- 멤버만으로 타입을 관계시키는 방법
- 즉, 구조가 같으면 같은 타입으로 간주함
- 타입의 확장에 열려있기 때문에, 같은 속성명을 가지면 다른 속성이 추가로 있는 타입과의 차이를 잡아낼 수 없음
구조적 타이핑의 장점
- 유닛 테스트에 유용함 (비교가 쉬움)
- 라이브러리 간의 의존성을 완벽하게 분리할 수 있음
아이템 5 - any 타입 지양하기
- 타입스크립트는 점진적이고(gradual), 선택적(optional)하다.
- 타입 추가 가능(gradual)
- 타입체커를 해제 가능(optional)
- 이러한 기능들은 any 타입으로 가능하게 됨.
any의 위험성
- 타입 안정성 X, ‘as any’와 같이 오류를 해결하기 위해 any를 사용한다면 타입의 혼돈이 생김.
- 함수 시그니처 무시, 함수를 호출 및 출력할 때 함수의 약속된 타입을 어기게 됨.
- 언어서비스 적용 X, 자동완성 기능과 도움말을 사용할 수 없음.
- 리팩토링때 버그를 감춤, any를 통해 타입체커가 통과한다 해도 잘못된 매개변수를 받으면 런타임에 오류각 발생하게 됨.
- 타입설계를 감춤, 복잡한 객체 정의를 간단하게 끝내버려 다른 동료들은 타입 설계를 어떻게 했는지 전혀 알수가 없음.
- 타입시스템의 신뢰도 저하, any를 통해서 타입 체커가 잡아지지 않으면 타입체커의 신뢰도가 하락되어 개발자가 직접 타입 오류를 고쳐야 함.
아이템 6 - 편집기를 사용하여 타입 시스템 탐색하기
- 타입 선언 파일 찾아보며 동작 확인
아이템 7 - 타입이 값들의 집합이라고 생각하기
- 타입 - 할당 가능한 값들의 집합
집합의 크기
never
- 아무 값도 포함하지 않는 공집합- 유닛(Unit), 리터럴 -
type A = 'a'
와 같이 한 가지 값만 포함 - 유니온(Union) - 여러개를 묶기 위해 파이프(
|
) 사용 - 타입 지정 -
string
,number
와 같이 무한대 범위를 지정 unknown
- universal set
교집합(&, intersection)
- 속성에 대해 합집합처럼 생각하면 된다
interface A { a: string }
interface B { b: string }
type C = A & B
const obj: C {
a: 'a',
b: 'b'
} // 정상
서브타입(subtype)
interface A extends B
아이템 8 - 타입 공간과 값 공간의 심벌 구분하기
- 심벌이 타입인지 값인지 구분해야 함
아이템 9 - 타입 단언보다는 타입 선언을 사용하기
- 변수에 값을 할당하고 타입을 부여하는 두 가지 방법>ㅁ
interface Person { name: string }
const alice: Person = { name: 'Alice' } // 타입 선언
const bob = { name: 'bob' } as Person // 타입 단언
- 함수 호출 체이닝이 연속되는 곳에서는 시작에서부터 명명된 타입을 가져야 함
- 타입 단언은 꼭 필요한 경우에 사용해야 함(DOM)
!
를 통해null
이 아님을 단언할 수 있음 (ex:document.getElementById('foo)!
)as unknown
을 통해 서로의 서브타입이 아닐 때 변환 가능(지양)
아이템 10 - 객체 래퍼 타입 피하기
- 객체 래퍼 타입은 지양하고, 기본형 타입을 사용해야 함
String > string
,Number > number
등..
string
은String
에 할당할 수 있지만, 반대의 경우는 되지 않음.- 따라서 타입스크립트에서 객체 래퍼 타입을 지양하고, 기본형 타입을 사용해야 한다.
BigInt
와 Symbol은 new가 없는 경우 기본형을 생성하여 사용해도 상관없음.typeof BigInt(1234) "bigint" typeof Symbol('sym') "symbol"
출처: 이펙티브 타입스크립트