아이템 1 . 타입스크립트와 자바스크립트 관계 이해하기
- 타입스크립트는 자바스크립트의 상위집합
- 타입스크립트는 자바스크립트에서 보장해주지 못한 런타임 에러를 타입 체커를 통해 컴파일 시점에 발견할 수 있게 해준다.
아이템 2. 타입스크립트 설정 이해하기
아래 코드는 타입 체크를 오류 없이 통과를 할까??
function add(a, b) {
return a+b;
}
add(10,null);
정답은 없다. 타입스크립트 설정 파일에 따라 다르기 떄문이다.
// tsconfig.json 파일
{
"compilerOptions:" :
{
"noImplicityAny" : true
}
}
- noImplicityAny : 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어
- 즉, 위의 예제는 타입을 지정하지 않았기 때문에 에러가 난다.
- strictNullChecks : null 과 undefined가 모든 타입에서 허용되는지 확인되는 설정
아이템 3. 코드 생성과 타입이 관계없음을 이해하기
타입스크립트 컴파일러가 하는 2가지 일
- 최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 구버전 자바스크립트로 transpile
- 코드의 타입 오류를 체크
이 2가지 일이 독립적으로 동작하기 때문에 코드의 타입오류가 있다고 해서 컴파일이 안되는 것은 아니다. 즉, 타입 체크를 통과하지 못하더라도 배포가 될 수 있다. 이를 방지하기 위해서는 아래와 같은 설정을 tsconfig.json에 해주면 된다.
noEmitOnError 설정
런타임에는 타입 체크가 불가능하다.
interface Square {
width: number;
}
interface Rectangle extends Square {
height: number;
}
type Shape = Square | Rectangle;
function calculateArea(shape: Shape) {
if (shape instanceof Rectangle) {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
instanceof 체크는 런타임에 일어나지만, Rectangle은 타입이기 때문에 런타임 시점에 아무런 역할을 할 수 없다.
클래스로 만들어버리면 타입과 값으로 모두 사용할 수 있으므로 위의 문제는 사라진다.
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) {
return shape.width * shape.height;
} else {
return shape.width * shape.width;
}
}
타입스크립트 타입으로는 함수를 오버로드할 수 없다.
타입스크립트에서 타입과 런타입의 동작이 무관하기 때문에 함수 오버로딩은 불가능하다.
function add(a:number,b:number) { return a+b; }
function add(a:string,b:string) { return a+b; }
타입스크립트 타입은 런타임 성능에 영향을 주지 않는다.
- 타입과 타입 연산자는 자바스크립트 변환 시점에 제거되기 때문에, 런타임의 성능에 아무런 영향 없음
- 타입스크립트 컴파일러는 '빌드타임'오버헤드가 있음. 오버헤드가 커지면, 빌드 도구에서 'transpile only'을 설정하여 타입 체크를 건너뛸 수 있음.
아이템 4. 구조적 타이핑에 익숙해지기
interface Vector2D {
x: number
y: number
}
function calcLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y)
}
위와 같은 코드가 있을때 아래 코드는 에러를 발생시킬까?
interface Vector2DWithName extends Vector2D {
name: string
}
const a: Vector2DWithName = { name: 'hi', x: 5, y: 10 }
calcLength(a) // works fine
interface Vector2DName {
name: string
x: number
y: number
}
const b: Vector2DName = { name: 'hello', x: 10, y: 10 }
calcLength(b) // works fine, too.
Kotlin 같은 언어였다면 아래 함수는 에러가 났을 것이다. 하지만 타입스크립트에서는 '구조적으로' 타입이 맞기만 한다면 이를 허용해준다. Structural typing
아래와 같은 경우는 예상하지 못한 에러를 발생시킬 수 있다.
interface Vector3D {
x: number
y: number
z: number
}
function normalize(v: Vector3D) {
const length = calcLength(v) // z가 고려되지 않음
return {
x: v.x / length,
y: v.y / length,
z: v.z / length // z의 값이 이상하게 나옴
}
}
normalize({x:3, y:4, z:5}) // 그러나 에러는 안남
타입스크립트의 타입은 열려있기 때문에 처음 접하게 되면 아래와 같은 실수를 많이 하게 된다.
function calcLengthV1(v: Vector3D) {
let length = 0
for (const axis of Object.keys(v)) {
const coord = v[axis] // Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Vector3D'.
// No index signature with a parameter of type 'string' was found on type 'Vector3D'.(7053)
length += Math.abs(coord)
}
return length
}
즉, 아래와 같은 예제에서 타입이 열려있기 때문에 예상치 못한 에러를 발생시킬 수 있다.
const v = { x: 1, y: 2, z: 3, name: 'hi, h i~' }
calcLengthV1(v) // name의 값이 NaN이라서 결과가 NaN으로 뜰 수 있다.
아래와 같이 작성하여야 의도치 않은 실수를 막을 수 있다.
function calcLengthV2(v: Vector3D) {
return Math.abs(v.x) + Math.abs(v.y) + Math.abs(v.z)
}
하지만 구조적 타이핑 덕분에 아래와 같이 편하게 코딩도 가능하다.
interface Employee {
name: string
id: number
}
interface DB {
runQuery: (sql: string) => any[]
}
function getEmployee(db: DB): Employee[] {
const rows = db.runQuery('SELECT name, id from EMPLOYEES')
return rows.map((row) => ({ name: row[0], id: row[1] }))
}
아이템 5. any 타입 지양하기
- 타입스크립트 언어 서비스를 무력화 시킴
- 리팩토링할때 위험
- 개발 경험, 신뢰도 뜰어뜨림
'Front-End' 카테고리의 다른 글
이펙티브 타입스크립트 5장 - any 다루기 (0) | 2022.02.06 |
---|---|
Next - 이미지 최적화 ( Image Component and Image Optimization ) (0) | 2021.12.15 |
가로세로 비율 유지하는 반응형 박스 (0) | 2021.12.15 |
클라이언트 UI와 서버 데이터 제대로 연동하고 싶다. (0) | 2018.02.23 |
Web project 성능 최적화하기 (0) | 2017.12.11 |