인터섹션 타입을 이용해 “여러 경우에 모두 해당”하는 타입을 표현할 수 있다.
A & B 타입의 값은 A 타입에도, B 타입에도 할당 가능해야 한다. 만약 A와 B 모두 객체 타입이라면 A & B 타입의 객체는 A와 B 타입 각각에 정의된 속성 모두를 가져야 한다.
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
type ElevatedEmployee = Admin & Employee;
const e1: ElevatedEmployee = {
name: 'max',
privileges: ['created-server'],
startDate: new Date(),
};
type 대신 아래와 같이 interface를 사용할 수 있다.
하지만 조금 더 짧은 type이 더 선호된다.
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type ElevatedEmployee = Admin & Employee;
위와 완전히 동일한 결과값을 같는다.
interface ElevatedEmployee extends Admin, Employee {}
A와 B 타입 모두에 할당 가능한 값이여야 한다.
type Combinable = string | number;
type Numeric = number | boolean;
type Universal = Combinalbe & Numeric;
let num: Universal = 1;
이 경우 number 타입이 공통적으로 할당되어 있기 때문에 Universal은 number 타입이 된다.
하지만 다음의 경우는 어떤 값도 만족하지 않는 인터섹션 타입이다.
type Infeasible = string & number
문자열인 동시에 숫자인 값은 존재하지 않으므로, 위 Infeasible 타입은 실제로는 어떤 값도 가질 수 없다.
인터섹션 타입이 런타입 시 정확히 어떤 타입을 얻게 될지 알아야 하는 경우가 많다.
타입가드는 특정 속성이나 메소드를 사용하기 전에 그것이 존재하는지 확인하거나 타입을 사용하기 전에 이 타입으로 어떤 작업을 수행할 수 있는지를 확인하는 개념 또는 방식을 나타낸다.
유니온 타입이 지닌 유연성을 활용할 수 있게 해주며 런타임 시 코드가 정확하게 작동하게 해 준다.
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformatino(emp: UnknownEmployee) {
// UnknownEmployee의 Employee, Admin 모두 name 속성이 있기 때문에 OK
console.log('Name: ' + emp.name);
// UnknownEmployee의 Employee에는 privileges의 속성이 없기 때문에 Error
console.log('Privileges: ' + emp.privileges); //=> Error
// in을 활용한 타입가드
// emp에 privileges 속성이 있는지 확인
if ('privileges' in emp) {
console.log('Privileges: ' + emp.privileges);
}
}
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 ('loadCargo' in vehicle) {
vehicle.loadCargo(1000);
}
// instanceof를 활용한 타입가드
if (vehicle instanceof Truck) {
vehicle.loadCargo(1000);
}
}
useVehicle(v1);
useVehicle(v2);
type 혹은 kind 라는 객체를 설명하는 속성을 switch문에 사용하여 완전한 타입 안전성을 갖추고, 객체에 어떤 속성을 사용할 수 있는지 파악할 수 있다.
실제 존재하는 속성을 사용하여 어떤 유형의 객체와 작업하고 있는지 확인할 수 있다.
또한 오타의 위험을 없애준다.
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 at speed : ' + speed);
}
moveAnimal({ type: 'bird', flyingSpeed: 10 });
const userInputElement = <HTMLInputElement>document.getElementById('user-input')!;
userInputElement.value = 'HI, THERE!';
react의 JSX 구문과 충돌하는 것을 막기 위해서 as를 사용한다.
const userInputElement = document.getElementById('user-input')! as HTMLInputElement;
userInputElement.value = 'HI, THERE!';
function add2(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add('max', 'schwarz') as string;
result.split(' ');
느낌표를 사용하여 느낌표 앞의 표현식을 null로 반환하지 않겠다고 타입스크립트에게 인식시킬 수 있다.
null을 반환할 수 있는 dom에서 이것이 null을 반환하지 않을 것이라는 서을 안다면 느낌표를 사용할 수 있다.
const userInputElement = <HTMLInputElement>document.getElementById('user-input')!;
null을 반환하지 않을 것이라는 확신이 들지 않는 경우 if문을 사용하면 된다.
하지만, 이 경우에는 형 변환을 제거해야 한다. 여기서 형 변환을 하면 null이 반환될 것이라는 확신이 없음에도 타입스크립트에게 null이 되지 않는다는 것을 알려주는 것이 되기 때문이다.
때문에 아래의 예시처럼 value에 접근해야 하는 경우 아래처럼 as HTMLInputElement를 추가하여 형 변환을 수행하면 된다.
const userInputElement = document.getElementById('user-input');
if (userInputElement) {
(userInputElement as HTMLInputElement).value = 'HI, THERE!';
}
인덱스 시그니처는 [key:type]: type 형식으로 객체가 여러 key를 가질 수 있으며, key와 매핑되는 value를 가지는 경우 사용한다.
key와 value의 타입을 정확하게 명시해야 하는 경우 사용할 수 있다.
interface ErrorContainer {
email: string;
[prop: string]: string;
}
const errorBag: ErrorContainer = {
email: 'Not a valid email!',
address: 'seoul',
username: 'capital',
};
매개변수가 다르며 이름이 동일한 함수를 오버로딩이라고 한다.
여러 개의 함수 선언과 하나의 함수 구현이 있는 함수가 존재한다.
함수에서 지원할 수 있는 다양한 조합에 대해 어떤 것이 반환되는지 명확하게 알 수 있다.
// 함수 선언
function add2(a: string, b: string): string;
function add2(a: number, b: number): number;
function add2(a: number, b: string): string;
function add2(a: string, b: number): string;
// 함수 구현
function add2(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}
const result = add2('max', ' schwarz');
result.split(' ');
HTTP 요청의 경우에서 객체의 일부 속성이 설정되어 있는지 또는 정의되지 않았는지 확실히 알 수 없는 중첩된 데이터로 구조화 작업을 수행하는 경우가 있다.
다음과 같이 &&을 사용하여 런타임 에러는 피할 수 있다.
const fetchedUserData = {
id: 'u1',
name: 'max',
job: { title: 'ceo', description: 'my company' },
};
console.log(fetchedUserData.job && fetchedUserData.job.title);
하지만 ts 더 나은 방법을 사용할 수 있다. 이 기능은 3.7버전 이상에서만 지원된다.
요소 다음에 물음표를 추가하면 해당 부분의 존재여부를 알려준다. 물음표 앞의 요소가 정의되지 않았다면 그 이후에는 해당 요소에 접근하지 않으므로 런타임 에러가 발생하지 않지만 에러가 지속되지 않는다.
const fetchedUserData = {
id: 'u1',
name: 'max',
job: { title: 'ceo', description: 'my company' },
};
//job이 정의되어 있는 경우 title에 접근할 수 있다.
console.log(fetchedUserData?.job?.title);
어떤 데이터가 null, undefined(빈 문자열, 0은 제외)일 경우에만 폴백을 사용해야 한다는 의미이다.
const userInput = '';
const storeData = userInput || 'Default';
const storeData2 = userInput ?? 'Default';
console.log(storeData); //=> Default
console.log(storeData2); //=> ''(빈문자열)