TypeScript 간단 정리 - 3
Union 타입 / Type Alias
유니온(Union) 타입 : (A || B)
자바스크립트의 OR 연산자 || 와 같은 의미
const printOut = (input: string | number) => {
console.log(input);
}
printOut('문자열');
printOut(20);
printOut(true);
간단하게 input이라는 파라미터를 받아서 해당 파라미터를 출력해주는 함수이다.
이 함수의 파라미터 input에는 문자열 타입 또는 숫자 타입이 올 수 있다. 함수 호출을 보면 string과 number는 할당이 되는데 boolean인 true 를 할당할때는 오류가 나는 것을 확인할 수 있다.
| 연산자를 이용하여 타입을 여러 개 연결하는 방식
유니온 타입의 장점
function getAge(age: any) {
age.toFixed(); // Error
return age;
}
getAge('20');
getAge(20);
위 코드에서 toFixed는 소수점 관련된 메소드 다 보니 age 가 숫자일 때만 실행이 가능하고 다른 자료형이 들어오면 에러가 발생한다.
이럴 때 유니온 타입을 활용할 수 있다.
function getAge(age: number | string) {
if (typeof age === 'number') {
age.toFixed();
return age;
}
if (typeof age === 'string') {
return age;
}
}
getAge('20');
getAge(20);
예를 들어 getAge라는 함수는 age파람에 number와 string만 들어올 수 있다라고 가정하면 유니온 타입을 활용해서 위와 같이 쓸 수 있다. 그리고 JS 의 typeof 연산자를 활용해서 숫자일때 string 일때 처리를 따로 해줄 수 있다.
function padLeft(value: string, padding: any) {
if (typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
}
if (typeof padding === 'string') {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
console.log(padLeft('Hello world', 4)); // " Hello world"
console.log(padLeft('Hello world', '!!!')); // "!!!Hello world"
console.log(padLeft('Hello world', true)); // Error
현재 함수의 param padding 타입이 any 이기 때문에 컴파일 타임에서 에러가 발생하지 않는다.
하지만 런타임에서 true 와 같은 자료형이 즉, number, string이 아닌경우가 들어가면 에러가 발생한다. 이럴때는 유니온타입으로 string | number 이렇게 써줘서 에러 상황을 컴파일 타임에서 알 수 있다.
Type Alias (사용자 정의 타입)
타입 별칭은 특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미한다.
const hero1: { name: string; power: number; height: number } = {
name: '슈퍼맨',
power: 1000,
height: 190,
};
const printHero = (hero: { name: string; power: number; height: number }) => {
console.log(hero.name, hero.power);
};
console.log(printHero(hero1));
매번 타입을 새로 작성하는 건 번거롭고 재사용이 불가능하다.
type 키워드를 사용해보자
// type.ts
type Hero = {
name: string;
power: number;
height: number;
};
import type { Hero } from './type';
const hero1: Hero = {
name: '슈퍼맨',
power: 1000,
height: 190,
};
const printHero = (hero: Hero) => {
console.log(hero.name, hero.power);
};
console.log(printHero(hero1));
위 코드처럼 자주 쓰이는 자료형들을 묶어서 관리를 할 수 있다.
type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT';
const printDirection = (direction: Direction) => {
console.log(direction);
};
printDirection('UP');
printDirection('UP!'); //Error
위 코드처럼 string 이라는 타입에서 타입을 'UP' | 'DOWN' | 'LEFT' | 'RIGHT' 로 좁힐 수 있다.
물론 type을 string 으로 할 수도 있지만, 4가지 방향을 의미하는 Direction type이 굳이 string 을 타입으로 가질 필요는 없다.
interface & Intersection Type
JAVA 같은 다른 언어에서 인터페이스는 클래스를 구현하기 전에 필요한 메서드를 정의하는 용도로 쓰이지만, TS에서는 좀 더 다양한 것들을 정의하는데 사용된다.
기본 정의
interface Person {
name: string;
age: number;
}
const person1: Person = { name: 'js', age: 20 };
const person2: Person = { name: 'ljs', age: 'twenty' }; // Error
선택 속성
interface Person {
name: string;
age?: number;
}
const person1: Person = { name: 'js' };
함수 타입에서 배운 optional parameter와 유사하다. ? 붙여주면 아래 person1 이라는 object 에 age 가 들어가도 되고 안들어가도 된다.
Read only 속성
interface Person {
readonly name: string;
age?: number;
}
const person1: Person = { name: 'js' };
person1.name = 'ljs'; // Error
let readOnlyArr: ReadonlyArray<number> = [1,2,3];
readOnlyArr.splice(0,1); // Error
readOnlyArr.push(4); // Error
readOnlyArr[0] = 100; // Error
읽기 전용 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 의미한다.
문법은 다음과 같이 readonly 속성을 앞에 붙인다. 수정하려고 하면 위와 같이 오류가 난다.
배열을 선언할 때 ReadonlyArray<T> 타입을 사용하면 읽기 전용 배열을 생성할 수 있다.
index type
interface Person {
readonly name: string;
[key: string]: string | number;
}
const p1: Person = { name: 'js', birthday: '비밀', age: 20 };
interface에서 속성 이름을 구체적으로 정의하지 않고 어떤 값의 타입만 정의하는 것을 인덱스 타입이라고 한다.
[key: string] ← key에 어떤 string 이든 들어올수있다를 의미한다.
따라서 이 interface index 키에는 string이 들어오고 value에는 string or number가 들어올 수 있다는 의미이다.
추가로 인덱스(키)에는 무조건 string , number만 들어올 수 있다.
함수 타입
interface Print {
(name: string, age: number): string;
}
// type Print = (name: string, age: number) => string;
const getNameAndAge: Print = function (name, age) {
return `name: ${name}, age: ${age}`;
};
interface로 함수 타입을 정의할 수 있다. Print interface 를 보면 왼쪽에 매개 변수를 입력하고 오른쪽에 반환 타입을 입력했다. (타입도 마찬가지)
인터페이스 확장
interface Person {
name: string;
age: number;
}
interface Korean extends Person {
birth: 'KOR';
}
interface Korean {
name: string;
age: number;
birth: 'KOR';
}
interface Developer {
job: 'developer';
}
interface KorAndDev extends Korean, Developer {}
interface KorAndDev {
name: string;
age: number;
birth: 'KOR';
job: 'developer';
}
인터페이스를 확장해서 새로운 인터페이스를 만들 수 있다. extends라는 키워드로 확장해서 새로운 interface를 만든다.
Korean interface를 예로 설명하면 확장은 Person 인터페이스를 그대로 활용하면서 괄호 안에 있는 type를 추가한 새로운 인터페이스 Korean를 만들겠다라는 의미이다.
intersection Type: 여러 타입을 모두 만족하는 하나의 타입
interface Person {
name: string;
age: number;
}
interface Developer {
name: string;
skill: string;
}
type DevJob = Person & Developer;
const nbcPerson: DevJob = {
name: 'a',
age: 20,
skill: 'ts',
};
Person 과 Developer 라는 인터페이스가 있다고 할 때, 이 두 인터페이스를 intersection type를 활용하여 하나로 합칠 수 있다.
DevJob type를 가지는 nbcPerson obj를 보면 Person 과 Developer 가 가지고 있는 모든 속성을 가지고 있는 것을 알 수 있다.
아래 벤다이그램 참고

type vs interface
타입 별칭과 인터페이스의 가장 큰 차이점은 타입의 확장 가능 / 불가능 여부이다. 인터페이스는 확장이 가능한데 반해 타입 별칭은 확장이 불가능하다.
따라서, 가능하다면 type 보다는 interface로 선언해서 사용하는 것을 추천한다.