December 2, 2021 • ☕️ 7 min read
타입스크립트 알아보기
“타입스크립트는 자바스크립트의 상위집합(superset)이다”
그렇기 때문에 자바스크립트 코드는 이미 타입스크립트다.
타입 시스템에서는 런타임에 오류를 발생시킬 코드를 미리 찾아낸다
const states = [
{ name: "Alabama", capital: "Montogmery" },
{ name: "Alaska", capital: "Juneau" },
{ name: "Arizona", capital: "Phoenix" },
// ...
];
for (const state of states) {
console.log(state.capitol); // JS에서는 undefined, TS에서는 에러
}
타입을 명시적으로 선언하여 의도를 분명하게 하면 오류를 구체적으로 알 수 있다.
interface State {
name: string;
capital: string;
}
const states: State[] = [
{ name: "Alabama", capitol: "Montogery" },
{ name: "Alaska", capitol: "Juneau" },
{ name: "Arizona", capitol: "Phoenix" },
];
// 🚨 Error
// 'State' 형식에 'capitol'이 없습니다.
// 'capital'을(를) 쓰려고 했습니까?
for (const state of states) {
console.log(state.capital);
}
타입스크립트 타입 시스템은 자바스크립트의 런타임 동작을 ‘모델링’한다
런타임에서 정상 동작하는 코드에 타입스크립트가 오류를 표시하는 경우
const a = null + 7;
// ✅ JS에서는 a의 값이 7이 된다.
// 🚨 TS Error: '+' 연산자를 ... 형식에 적용할 수 없습니다.
tsconfig.json으로 타입스크립트 설정 작성하기
{
"compilerOptions": {
// ...
}
}
noImplicitAny
- 변수들이 미리 정의된 타입을 가져야 하는지 여부를 제어
function add(a, b) {
return a + b;
}
add
부분에 hover 시 타입스크립트가 추론한 함수의 타입 확인 가능
function add(a: any, b: any): any;
→ 이를 암시적 any라고 부른다
noImplicitAny
가 설정되었다면 오류 발생
strictNullChecks
null
과 undefined
가 모든 타입에서 허용되는지 확인한다
// strictNullChecks 해제 시
const x: number = null; // 정상
// strictNullChecks 설정 시
const x: number = null; // 🚨 에러: 'null' 형식은 'number' 형식에 할당할 수 없습니다.
null
을 사용하지 않으려면 null
을 체크하는 코드나 단언문을 추가해야 한다
const el = document.getElementById("status");
el.textContent = "Ready"; // 🚨 에러
if (el) {
el.textContent = "Ready"; // 정상, null을 제외
}
el!.textContent = "Ready"; // 정상, el이 null이 아님을 단언
타입스크립트에서 엄격한 체크를 하고 싶다면 strict
설정을 고려
타입스크립트 컴파일러는 두 가지 역할을 수행한다.
타입 오류가 있는 코드도 컴파일이 가능하다.
런타임에는 타입 체크가 불가능하다.
타입스크립트의 타입은 ‘제거 가능’하다. 즉 자바스크립트로 컴파일되는 과정에서 모든 인터페이스, 타입, 타입 구문은 그냥 제거되어 버린다
런타임에 타입 정보를 유지하는 방법
특정 속성이 존재하는지 체크
‘태그’ 기법 - 런타임에 접근 가능한 타입 정보를 명시적으로 저장
interface Square {
kind: "square";
width: number;
}
interface Rectangle {
kind: "rectangle";
height: number;
width: number;
}
type Shape = Square | Rectangle; // '태그된 유니온(tagged union)'
function calculateArea(shape: Shape) {
if (shape.kind === "rectangle") {
shape; // 타입이 Rectangle
return shape.width * shape.height;
} else {
shape; // 타입이 Square
return shape.width * shape.width;
}
}
타입(런타임 접근 불가)과 값(런타임 접근 가능)을 둘 다 사용하는 기법
타입 연산은 런타임에 영향을 주지 않는다
런타임 타입은 선언된 타입과 다를 수 있다.
switch~case
구문의 default
구문타입스크립트 타입으로는 함수를 오버로드할 수 없다.
function add(a: number, b: number) {
return a + b;
} // 🚨 에러: 중복된 함수 구현입니다.
function add(a: string, b: string) {
return a + b;
} // 🚨 에러: 중복된 함수 구현입니다.
타입스크립트의 함수 오버로딩은 타입 수준에서만 가능하다 (구현체는 불가)
function add(a: number, b: number): number;
function add(a: string, b: string): string;
타입스크립트 타입은 런타임 성능에 영향을 주지 않는다
자바스크립트는 본질적으로 덕 타이핑(duck typing) 기반
🐤 덕 타이핑 객체가 어떤 타입에 부합하는 변수와 메서드를 가질 경우 객체를 해당 타입에 속하는 것으로 간주하는 방식
타입스크립트는 이 동작을 그대로 모델링한다.
예제
interface Vector2D {
x: number;
y: number;
}
function calculateLength(v: Vector2D) {
return Math.sqrt(v.x * v.x + v.y * v.y);
}
interface NamedVector {
name: string;
x: number;
y: number;
}
NamedVector
의 구조가 Vector2D
와 호환되기 때문에 calculateLength
함수 호출 가능🚨 문제
interface Vector3D {
name: string;
x: number;
y: number;
}
function normalize(v: Vector3D) {
const length = calculateLength(v);
return {
x: v.x / length,
y: v.y / length,
z: v.z / length,
};
}
normalize({ x: 3, y: 4, z: 5 });
// { x: 0.6, y: 0.8, z: 1 }
Vector3D
는 calculateLength
함수 호출 시 Vector2D
와 호환된다z
가 정규화에서 무시되기 때문에 잘못된 결과를 출력한다구조적 타이핑과 클래스 할당문
class C {
foo: string;
constructor(foo: string) {
this.foo = foo;
}
}
const c = new C("instanceof C");
const d: C = { foo: "object literal" }; // 정상
Object.prototype
으로부터 비롯된 생성자를 가진다구조적 타이핑을 사용하면 유닛 테스트를 쉽게 할 수 있다
interface DB {
runQuery: (sql: string) => any[];
}
function getAuthors(database: DB): Author[] {
const authorRows = database.runQuery(`SELECT FIRST, LAST FROM AUTHORS`);
return authorRows.map((row) => ({ first: row[0], last: row[1] }));
}
any 타입에는 타입 안정성이 없다
let age: number;
age = "12" as any; // 정상
age += 1; // 런타임에 정상, 🚨 age는 '121'
any는 함수 시그니처를 무시해 버린다
function calculateAge(birthDate: Date): number {
// ...
}
let birthDate: any = "1997-09-12";
calculateAge(birthDate); // 정상 (🚨 추후 에러 발생 가능)
any 타입에는 언어 서비스가 적용되지 않는다
any 타입은 코드 리팩터링 때 버그를 감춘다
any는 타입 설계를 감춰버린다
any는 타입시스템의 신뢰도를 떨어뜨린다
Ref 이펙티브 타입스크립트 1장: 타입스크립트 알아보기