인터섹션 타입
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
// 여기
type ElevatedEmlpoyee = Admin & Employee;
const e1: ElevatedEmlpoyee = {
name: 'KIM',
privileges: ['create-server'],
startDate: new Date()
}
interface Admin {
name: string;
privileges: string[];
};
interface Employee {
name: string;
startDate: Date;
};
interface ElevatedEmlpoyee extends Admin, Employee {}
const e1: ElevatedEmlpoyee = {
name: 'KIM',
privileges: ['create-server'],
startDate: new Date()
}
인터섹션 타입은 인터페이스 상속과 밀접한 관련이 있습니다. 즉, 두 코드는 정확히 같은 기능을 합니다.
객체 타입은 객체 속성의 조합을 나타냅니다.
type Combinable = string | number;
type Numeric = number | boolean;
type Universal = Combinable & Numeric;
숫자형 타입이 유일한 인터섹션 타입이기 때문에 Universal은 숫자형 타입으로 간주합니다.
유니언 타입은 타입 간에 공통점이 있는 타입을 나타냅니다.
인터섹션 연산자는 어떤 타입과도 사용할 수 있어서 이렇게 타입들이 교차하도록 간단하게 구현할 수 있습니다.
타입가드
typeof 타입가드
type Combinable = string | number;
function add(a: Combinable, b: Combinable) {
// 이 조건문을 타입가드라고 함
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
위 코드와 같이 정확히 무슨 작업을 수행하는지 타입에 따라 달라지게 할 수 있습니다.
타입가드는 유니온 타입이 지닌 유연성을 활용할 수 있게 해 주며 런타임시 코드가 정확하게 작동하게 해 줍니다.
객체에 관한 타입가드는 typeof를 쓸 수 없고 in과 instanceof 타입가드를 쓸 수 있습니다.
in 타입가드
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
// 두 사용자 정의 객체 타입을 사용하여 유니언 타입을 만듬
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log('Name: ' + emp.name); // 에러 아님
console.log('Privileges: ' + emp.privileges); // 에러
}
위 코드에선 UnknownEmployee에 두 객체 모두 들어올 수 있기 때문에 name 속성은 둘 다 가지고 있기에 에러가 아니지만 privileges 속성은 Admin만 가지고 있기에 타입가드가 필요합니다.
사용자 정의 객체 타입에는 typeof를 사용할 수 없기에 in 키워드를 이용하여 해결할 수 있습니다.
function printEmployeeInformation(emp: UnknownEmployee) {
console.log('Name: ' + emp.name);
if('privileges' in emp) {
console.log('Privileges: ' + emp.privileges);
}
if('startDate' in emp) {
console.log('Start Date: ' + emp.startDate);
}
}
instanceof 타입가드
class Car {
drive() {
console.log('Driving...')
}
}
class Truck {
drive() {
console.log('Driving a truck...')
}
loadCargo(amount: number) {
console.log('Loading cargo ...' + amount)
}
}
type Vehicle = Car | Truck;
const v1 = new Car();
const v2 = new Truck();
function useVehicle(vehicle: Vehicle) {
vehicle.drive();
if (vehicle instanceof Truck) {
vehicle.loadCargo(1000);
}
}
in 타입가드를 사용할 때 문자열에서의 오류 검증이 힘들 수 있기에 더 견고한 방법인 instaceof 사용도 있습니다.
instanceof는 순수 자바스크립트 기능이기에 인터페이스에서 사용할 수 없습니다.
따라서 사용하게 되면 객체가 클래스에 기반하는지 확인할 수 있고 클래스에 기반한다면 그 메서드를 지니고 있는지 확인할 수 있습니다.
구별된 유니언
interface Bird {
// 여기
type: 'bird';
flyingSpeed: number;
}
interface Horse {
// 여기
type: 'horse';
runningSpeed: number;
}
type Animal = Bird | Horse;
function moveAnimal(animal:Animal) {
let speed;
switch (animal.type) {
case 'bird':
speed = animal.flyingSpeed;
break;
case 'horse':
speed = animal.runningSpeed;
}
console.log('Moving with speed: ' + speed);
}
위 코드는 인터페이스를 통해 생성된 객체이기 때문에 타입가드를 instanceof로 할 수 없으며 in을 사용할 순 있지만 경우의 수가 늘어날 경우 복잡하고 실수하기 쉽습니다.
그때 이 방법도 활용할 수 있는데 인터페이스마다 공통되는 속성을 적용하고(여기서는 type속성) switch문을 통해 케이스를 나눴습니다.
이 경우에는 in 타입가드와 다르게 케이스에 자동 완성을 표시해 주기 때문에 실수를 줄일 수 있습니다.
형 변환(typecasting)
index.html
...
<body>
<input type="text" id="user-input">
</body>
...
app.ts
const userInputELement = document.getElementById('user-input');
위와 같이 코드를 작성해도 userInputElement는 html에 있는 저 인풋 태그를 감지할 능력이 없습니다.
이 문제를 형 변환으로 타입스크립트가 직접 감지하지 못하는 특정 타입의 값을 타입스크립트에 알려줘 해결할 수 있습니다.
하나는 변환하고자 하는 요소 앞이나 타입스크립트에 타입을 알려주고자 하는 위치 앞에 무언가를 추가하는 방법
두 개의 방법이 있습니다.
const userInputELement = <HTMLInputElement>document.getElementById('user-input')!;
하나는 이렇게 변환하고자 하는 요소 앞에 <HTMLInputElement>를 추가하여 형 변환합니다.
const userInputELement = document.getElementById('user-input')! as HTMLInputElement;
리액트 JSX 구문과의 충돌을 막기 위해 만든 대안이며 선택한 부분 다음에 as 키워드와 어떤 타입으로 형 변환할지 입력하면 됩니다.
선택 요소 마지막에 들어간 느낌표는 앞의 표현식을 null로 반환하지 않겠다고 타입스크립트에게 인식시키는 역할을 합니다. 이 느낌표에 대안으로는 다음과 같이 작성할 수 있습니다.
const userInputELement = document.getElementById('user-input');
if(userInputELement) {
(userInputELement as HTMLInputElement).value = 'Hi there!';
}
인덱스 타입
객체가 지닐 수 있는 속성에 대해 보다 유연한 객체를 생성할 수 있게 해주는 기능입니다.
interface ErrorContainer {
id: number; // 에러
[prop: string]: string; // 인덱스 타입
}
정확한 속성 이름을 모르고 속성의 개수도 모르며 이 인터페이스 기반의 객체에 추가되는 모든 속성은 문자열로 해석할 수 있는 속성 이름을 지녀야 한다는 것과 해당 속성에 대한 값 역시 문자열 이어야 한다는 것만 알고 있다고 입력한 것입니다.
그래서 id 속성에 number를 할당할 경우 다음과 같은 에러가 발생합니다.
interface ErrorContainer {
[prop: string]: string;
}
const errorBag: ErrorContainer = {
email: 'Not a valid email!',
username: 'Must start with a capital character!'
}
함수 오버로드
동일한 함수에 대해 여러 함수 시그니처를 정의할 수 있는 기능입니다.
간단히 말해 다양한 매개변수를 지닌 함수를 호출하는 여러 가지 가능한 방법을 사용하여 함수 내에서 작업을 수행할 수 있게 해 줍니다.
type Combinable = number | string;
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add('Kim', 'iltae');
result.split('') // 에러
위 코드에서 우리는 result가 문자열이라는 걸 알고 있지만 사실 타입스크립트는 알지 못합니다.
그래서 문자열 메서드인 split을 사용하면 다음과 같은 에러가 나옵니다.
const result = add('Kim', 'iltae') as string;
물론 다음과 같이 형 변환을 하면 해결할 수 있지만 코드를 더 사용해야 하고 최선은 아니기에 함수 오버로드를 사용하는 것도 좋습니다.
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add('Kim', 'iltae');
result.split('')
이렇게 작성한 함수 위에 오버로드를 작성해 타입스크립트가 자체적으로 반환 타입을 정확히 추론하지 못하는 경우에 사용할 수 있습니다.
함수에서 지원할 수 있는 다양한 조합에 대해 어떤 것이 반환되는지 명확하게 할 수 있습니다.
선택적 체이닝
객체의 일부 속성이 설정되어 있는지 또는 정의되지 않았는지 확실히 알 수 없는 중첩된 데이터로 구조화 작업을 수행하는 경우 사용할 수 있습니다.
const fetchedUserData = {
id: 'a1',
name: 'KIM',
job: { title: 'CEO', description: 'My own company' }
};
// 여기 여기
console.log(fetchedUserData?.job?.title);
선택적 체이닝 연산자는 객체 데이터의 중첩된 속성과 객체에 안전하게 접근할 수 있게 해 줍니다.
물음표 앞의 요소가 정의되지 않았다면 그 이후에는 해당 요소에 접근하지 않습니다.
이는 데이터의 존재 여부를 확인하는 if 문으로 컴파일됩니다.
Null 병합
const userInput = null;
// 여기
const storedData = userInput ?? 'DEFAULT';
userInput이 null이거나 undefined라면 'DEFAULT'를 할당하겠다는 것을 의미합니다.
이는 아래 코드와 다르게 userInput이 falsy 한 값 즉 0 또는 빈 문자열('')도 같이 처리하는 것이 아닌 오직 null과 undefined일 경우만 처리합니다.
const userInput = null;
const storedData = userInput || 'DEFAULT';