-
이펙티브 타입스크립트 아이템 1~3TypeScript 2023. 6. 2. 15:38728x90
아이템1: 타입스크립트와 자바스크립트의 관계 이해하기
TypeScript는 JavaScript의 상위 집합입니다.
따라서 main.js를 main.ts로 변경하여도 달라지는 것은 없습니다. 이것은 JavaScript를 TypeScript로 마이그레이션하는데 큰 도움을 줍니다.
다음 예제를 확인해보겠습니다.function greet(who: string) { console.log('Hello', who) }
JavaScript는 who: string에 대해서 Unexcepted Token 에러를 발생시킵니다.
function greet(who) { console.log('Hello', who) }
하지만 위의 예제를 TypeScript에서는 에러가 발생하지 않습니다. 따라서 JavaScript의 상위집합이라고 할 수 있습니다.
다음 예제를 JavaScript에서 돌려보겠습니다.
let city="new york city" console.log(city.toUppercase()) //타입스크립트인 경우 아래와 같은 문구가 보이게됩니다. //'toUppercase' 속성이 string 형식에 없습니다. //'toUpperCase' 를 사용하시겠습니까?
string을 대문자로 바꿔주는 함수는 toUpperCase() 이므로 에러가 발생하지만 이유가 무엇인지 알기 힘듭니다. 하지만 typescript는 타입 추론을 통해서 city가 string임을 추론하기 때문에 string의 메소드인 toUpperCase 임을 유추하여 알려줄 수 있습니다.
아래의 예제도 javascript에서 실행해보겠습니다.
const states = [ {name: 'Alabama', capital: 'Montgomery'}. {name: 'Alaska', capital: 'Juneau'}. ] for(const state of states) { console.log(state.capitol) } //출력 결과 //undefined //undefined
typescript는 다음과 같이 오류를 찾아냅니다.
for(const state of states) { console.log(state.capitol) //'capitol'속성이 state형식에 없습니다. //'capital'을 사용하시겠습니까? }
과연 이 오류가 정확한 오류인가 생각했을 때, 우리가 states에 capitol 속성을 입력하고 싶었을 수 있기 때문에 정확한 오류라고 볼 수는 없습니다.따라서 아래와 같이 명시적인 type을 선언하여 보다 명확한 오류를 잡을 수 있게 하는 것이 좋습니다.
interface State { name: string capital: string }
따라서 아래와 같은 벤 다이어그램이 나올 수 있습니다.
typescript 타입 시스템은 전반적으로 자바스크립트의 동작을 모델링합니다. 하지만 자바스크립트에서 허용되지만 타입스크립트에서는 문제가 되는 경우도 있습니다. 이러한 문법의 엄격함은 취향 차이이므로 아래와 같은 상황이 익숙하다면 사용하지 않는게 나을 수 있습니다.
const x = 2 + '3' // '23' const a = null + 7 // 7 const b = [] + 12 // 12
아이템2 : 타입스크립트 설정 이해하기
아래의 코드가 타입 체커를 통과 할 수 있을까요?
function add(a, b) { return a + b } add(10, null)
정답은 모릅니다. 입니다. 설정이 어떻게 되어있는지 모르기 때문입니다.
tsconfig.js를 통해 엄격도를 설정하는 것이 좋습니다. 그래야 다른 동료 개발자들이 어떻게 타입스크립트를 사용할 계획인지 알기 쉽습니다. 설정 파일은 tsc --init을 실행하여 쉽게 생성할 수 있습니다.
설정에서 가장 중요한 noImplicitAny와 strictNullChecks를 이해해 보겠습니다.
noImplicitAny
noImplicitAny는 변수들이 미리 정의된 타입을 가져야하는지 여부를 제어합니다.
noImplicitAny가 켜져있지 않다면 아래의 코드는 다음과 같이 추론됩니다.
function add (a, b) { // 변수 a, b는 any로 추론 됩니다. return a + b }
하지만 같은 코드에 noImplicitAny가 켜져있다면 오류가 됩니다. 반드시 어떠한 타입을 선언해 주어야 합니다.
타입스크립트는 타입 정보를 가질 때 가장 효과적이기 때문에 noImplicitAny를 켜주는 것이 좋습니다.
만약 javascript를 typescript로 마이그레이션 하고있다면 noImplcitAny를 끈 상태로 진행하면 됩니다.
strictNullChecks
strictNullChecks는 null과 undefined가 모든 타입에서 허용되는지 확인하는 설정입니다.
strictNullChecks가 켜져있지 않다면 아래의 코드는 다음과 같이 추론됩니다.
const x: number = null // null은 유효한 값입니다.
켜져있다면 다음과 같습니다.
const x: nmber = null // null형식은 number 형식에 할당할 수 없습니다.
undefined도 null과 같습니다. 만약 null이나 undefined를 사용해야 한다면 명시해줘야 합니다.
const x: number | null = null
아이템3 : 코드 생성과 타입이 관계없음을 이해하기
타입스크립트 컴파일러는 크게 두 가지 역할을 수행합니다.
- 최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구버전의 자바스크립트로 트랜스파일합니다.
- 코드의 타입 오류를 체크합니다.
여기서 중요한 것은 위 두 가지 역할은 독립적으로 수행된다는 것 입니다.
타입 오류가 있는 코드도 컴파일이 가능하다.
타입에 오류가 있는 코드도 컴파일이 가능합니다. 이 때문에 타입스크립트가 엉성한 언어로 보일 수 있지만 웹에선 이 부분이 도움이 됩니다. 어떤 코드에 문제가 발생했을 때 타입스크립트는 컴파일된 산출물을 생성하기 때문에 웹의 다른 부분을 테스트 할 수 있게 됩니다. 만약 오류가 있을 때 컴파일을 하고싶지 않다면 noEmitOnError옵션을 켜주면 됩니다.
더보기타입스크립트를 사용하면서 코듣에 오류가 있을 때 "컴파일에 문제가 있다"라고 말하는 경우는 엄밀히 말하면 기술적으로 틀린 말입니다. 타입스크립트에서 컴파일은 코드의 생성 이기 때문에 유효한 자바스크립트로 변환 가능하다면 컴파일이 가능하고, 이 때 발생하는 타입 에러는 "타입 체크에 문제가 있다." 라고 보는 것이 맞습니다.
런타임에는 타입 체크가 불가능합니다.
interface Square { width: number; } interface Rectangle extends Square { height: number; } type Shape = Square | Rectangle; function calculateArea(shape: Shape) { if(shape instanaceof Rectangle) { //shape의 형식이 Rectangle인지 확인했는데 아래에선 값을 사용하므로 에러가 발생합니다. return shape.width * shape.height } else { return shape.width * shape.width } }
instanceof는 런타임에 일어나지만 타입은 런타임 시점에 아무 역할도 할 수 없습니다. 실제로 타입스크립트가 자바스크립트로 컴파일되면 타입, 인텊이스, 타입 구문은 제거됩니다.
shape 타입을 런타임에도 유지하려면 아래와 같은 방법을 사용할 수 있습니다.
function calculateArea(shape: Shape) { if('height' in shape) { return shape.width * shape.height } else { return shape.width * shape.width } }
또 다른 방법으로는 태그 기법이 있습니다.
interface Square { kind: 'square' width: number } interface Rectangle { kind: 'rectangle' height: number width: number } type Shape = Square | Rectangle function calculateArea(shape: Shape) { if(shape.kind === 'rectangle') { return shape.width * shape.height } else { return shape.width * shape.width } }
타입 연산은 런타임에 영향을 주지 않습니다.
function asNumber(val: number | string) { return val as number; }
위 예제는 타입 체커는 통과 되지만 잘못된 방법입니다.
여기서 as number는 타입 연산이고 런타임 동작에 아무 영향도 주지 않습니다. 값을 정제하기 위해선 아래의 방법을 사용하는게 좋습니다.
function asNumber(val: number | string) { return tyepof(val) === 'string'? Number(val) : val }
런타임 타입은 선언된 타입과 다를 수 있습니다.
function setLightSwitch(value: boolean) { switch(value) { case true: turnLightOn() break case false: turnLightOff) break default: console.log('실행 될라나') } }
위 코드에서 value는 boolean이므로 default 부분이 필요 없다고 느낄 수 있습니다.
하지만 런타임 상황에 value가 서버로부터 문자열로 넘어온다면 default 부분이 실행될 수 있습니다.
따라서 타입 체크가 이루어 졌더라도 선언된 타입이 언제든 바뀔 수 있다는 점을 명심해야 합니다.
타입스크립트 타입으로는 함수를 오버로드할 수 없습니다.
타입스크립트에서 타입은 런타임 동작과 무관하기 때문에 오버로딩이 불가능합니다. 따라서 여러개의 선언문은 작성할 수 있지만 구현체는 하나입니다.
function add(a: number, b: number) { return a + b; } // ~~~ Duplicate function implementation function add(a: string, b: string) { return a + b; } // ~~~ Duplicate function implementation
타입스크립트 타입은 런타임 성능에 영향을 주지 않습니다.
타입과 타입 연산자는 자바스크립트로 변환되는 시점에(컴파일) 런타임의 성능에 아무런 영향을 주지 않습니다.
728x90'TypeScript' 카테고리의 다른 글
[TypeScript] 선언병합 (1) 2022.09.23 [TypeScript] 고급타입 - 2 (1) 2022.09.22 [TypeScript] 고급타입-1 (0) 2022.09.21 [TypeScript] 제네릭 (0) 2022.09.17 [TypeScript] 열거형 (1) 2022.09.16