* TypeScript 등장 배경
* TypeScript 장점
* TypeScript의 데이터 타입
TypeScript(타입스크립트)
TypeScript는 마이크로소프트에서 개발한 JavaScript의 상위 집합(Superset) 언어이다.
- 상위 집합 언어는 기존 언어의 문법, 기능 및 기능을 모두 포함하면서 추가적인 기능이나 문법을 제공한다.
- JavaScript에 정적 타입 검사와 클래스 기반 객체 지향 프로그래밍 등의 기능을 추가하여 개발된 언어이다.
- 정접 타입 검사는 JavaScript의 동적 타입 시스템에 비해 변수의 타입을 명시적으로 선언하고 타입 검사를 수행할 수 있게 해준다는 의미이다.
- 이는 개발자가 런타임에서 발생할 수 있는 타입 관련 오류를 사전에 감지하고 방지할 수 있게 한다.
- JavaScript가 발전하면서 생긴 단점을 보완하기 위해 등장하게 되었다.
TypeScript의 등장 배경
JavaScript는 처음에는 브라우저에서만 동작하는 스크립팅 언어로 만들어졌다.
- 시간이 점점 흐르고, JavaScript로 웹 애플리케이션의 상호작용이 증가하면서, 웹 애플리케이션이 필요로 하는 JavaScript 코드의 양이 폭발적으로 늘어나게 되었다.
- JavaScript는 태생이 브라우저에서 동작하는 스크립팅 언어였던 만큼, 폭발적으로 사용량이 증가하게 되며 단점 및 한계가 드러나게 되었다.
- JavaScript는 동적 타입이 결정되어 유연하고, 다양한 라이브러리와 프레임워크를 사용할 수 있는 장점이 있지만,
- 타입의 명시성이 부족하다는 단점이 있다.
- 타입의 명시성이 부족하게 되면 예상치 못한 결과를 초래할 수 있다. 아래는 그 예시이다.
- add 함수에 숫자 5와 문자열 "7"을 전달인자로 전달하여 덧셈을 수행하고 있다. 이렇게 되면 결과는 "57"이 나온다.
- 이런 식으로 JavaScript는 문자열과 숫자를 더할 경우, 숫자 타입의 인수 쪽을 강제적으로 타입 변환해 문자열을 만든다.
- 이렇게 JavaScript는 함수나 변수의 타입을 명시적으로 지정하지 않아도 동작하는 경우가 많아 예상치 못한 결과를 초래하게 된다.
let add = (x, y) => {
return x + y;
}
add(5, "7");
이런 문제점을 보완하기 위해 TypeScript라는 언어가 등장하게 되었다.
TypeScript를 사용했을 시 장점
TypeScript는 정적타입 검사 기능을 제공하며, 코드의 가독성과 유지 보수성을 높여준다.
- 이를 통해 개발자는 런타임 에러를 최소화하고, 코드 작성 시간을 단축하며, 협업 시 코드의 가독성을 높일 수 있다.
- 또한 TypeScript는 ES6의 문법을 포함한 최신 JavaScript 문법을 지원하며, 인터페이스(Interface), 제네릭(Generic), 데코레이터(Decorators) 등의 기능을 제공하여 객체 지향 프로그래밍을 보다 쉽게 할 수 있도록 도와준다.
- 아래는 인터페이스(Interface)를 사용하여 코드의 가독성을 높인 예시이다.
- User 인터페이스를 정의해 User의 정보를 좀 더 쉽게 파악할 수 있다.
- greetingUser 함수에도 매개변수로 User 타입을 사용해 이 함수가 어떤 타입의 인자를 받고 있는지 명확히 표현하고 있다.
- 이렇게 TypeScript를 사용하면 코드의 가독성을 높일 수 있다.
- 또한 타입을 명시함으로써 코드의 의도 또한 명확해지기 때문에 다른 개발자가 코드를 이해하고 수정하기 쉬워지며, 런타임 에러를 미리 방지할 수 있기 때문에 유지보수성 또한 높아진다.
interface User {
id: number;
name: string;
}
function greetingUser(user: User) {
console.log(`Hello, ${user.name}!`)
}
const parkUser = {
id: 1,
name: "박해커"
};
greetingUser(parkUser);
TypeScript의 데이터 타입
TypeScript는 JavaScript에서 지원하는 타입의 대부분을 지원하고, 그 외에 추가로 타입을 명시하여 JavaScript 언어의 한계를 보완한다.
Boolean(불리언) 타입
가장 기본적인 데이터 타입으로, JavaScript에서도 마찬가지로 boolean 값이라고 불리는 참(true), 거짓(false) 값이다.
let isShow: boolean = true;
let isDone: boolean = false;
Number(숫자) 타입
TypeScript에서 Number 타입을 선언하는 방식은 아래와 같다.
- JavaScript와 마찬가지로 TypeScript 또한 정수와 실수의 구분 없이 Number 타입 하나로 표기한다.
- TypeScript는 이 외에도 추가로 bigint를 지원한다.
let number1: number = 5;
let number2: number = 0.7;
String(문자열) 타입
TypeScript는 JavaScript처럼 큰따옴표(")나 작은따옴표(')를 사용하여 문자열 데이터를 표현한다.
- 또한 백틱(`)을 사용한 문자열인 템플릿 리터럴을 사용하면 여러 줄에 걸쳐 문자열을 작성할 수 있다.
let firstName: string = "coding";
let lastName: string = 'kim';
let longString: string = `Kimcoding is a developer.
He is 20 years old.`
Array(배열) 타입
TypeScript는 JavaScript처럼 값들을 배열로 다룰 수 있게 할 수 있으며, 두 가지 방법으로 배열 타입을 선언해 사용할 수 있다.
- 첫 번째 방법은 배열의 요소들을 나타내는 타입 뒤에 배열을 나타내는 []을 쓰는 것이다.
- 이어 두 번째 방법은 제네릭 배열 타입을 사용하는 것이다.
- Array를 먼저 작성한 뒤, <> 안에 배열의 요소들을 나타내는 타입을 작성한다.
- 배열 타입은 기본적으로 하나의 타입만 작성하게 되어 있으며, 타입을 혼용해서 작성하는 것은 불가능하다.
//첫 번째 방법
let items: string[] = ["apple", "banana", "grape"];
//두 번째 방법
let numberList: Array<number> = [4, 7, 100];
Tuple(튜플) 타입
TypeScript에서 튜플 타입을 사용하면 요소의 타입과 개수가 고정된 배열을 표현할 수 있다.
let user: [string, number, boolean] = ["kimcoding", 20, true];
모든 요소가 전부 같을 필요는 없지만, 배열의 index마다 타입이 정해져 있기 때문에 정확한 index에 접근할 필요가 있다.
- 이렇게 user[2]에 접근하게 되면, user[2]에 있는 요소는 boolean 타입이기 때문에 타입 에러가 발생한다.
console.log(user[2].toString());
JavaScript에서도 튜플 타입을 지원하며, JavaScript에서의 튜플 또한 여러 개의 값을 가진 배열을 나타내는 데에 사용한다.
- 그러나 JavaScript에서는 튜플 타입을 명시적으로 선언할 수 없기 때문에 개발자가 직접 튜플의 각 요소의 타입을 확인하고 유추해야 하므로 타입 에러가 더 쉽게 발생한다.
- TypeScript의 등장 배경 중 하나인 타입 에러를 방지하고자 한 이유 중 하나이기도 하다.
Object(객체) 타입
TypeScript에서 객체는 JavaScript와 마찬가지로 원시 타입이 아닌 타입을 나타낸다.
- JavaScript에서 Object(객체) 타입은 프로퍼티를 가지는 JavaScript의 값을 말하며 typeof 연산자를 사용했을 때 “object”을 반환하는 모든 타입을 의미한다.
JavaScript의 원시 타입에는 number, string, boolean, undefined, null, symbol 이 있다.
TypeScript에서 object 타입은 모든 객체를 수용하는 타입으로, 객체의 프로퍼티 타입들이 any로 지정되기 때문에 어떠한 프로퍼티라도 추가할 수 있다.
- 그러나 이는 타입 안정성을 보장하지 않기 때문에 편하지만, 추천하는 방법은 아니다.
let obj: object = {};
따라서 객체의 프로퍼티 타입들을 각기 명시해 주는 것이 훨씬 좋다.
- 객체는 이런 방식으로 key-value에 구체적인 타입까지도 지정할 수 있다.
let user: {name: string, age: number} = {
name: "kimcoding",
age: 20
}
Any 타입
간혹 애플리케이션을 만들 때, 알지 못하는 타입을 표현해야 할 때도 있다.
- 클라이언트에서 유저로부터 받은 데이터 및 서드파티 라이브러리에서 들어오는 값인 경우 개발자가 알지 못하는 타입일 수 있다.
- 이 경우, 타입 검사를 하지 않고자 할 때 any 타입을 사용할 수 있다.
let maybe: any = 4;
any 타입을 사용하게 되면, 변수에 값을 재할당하는 경우 타입을 명시한 변수와 달리 타입에 구애받지 않고 값을 재할당할 수 있게 된다.
let obj: object = {};
//에러
obj = "hello";
let maybe: any = 4;
//정상적으로 동작
maybe = true;
또한 엄격한 타입 검사를 진행하지 않기 때문에, 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티로 접근해도 에러가 나지 않는다.
- 대신, 실제 할당된 값이 가지지 않는 메서드 및 프로퍼티이기 때문에 반환되는 값은 undefined이다.
let maybe: any = 4;
//undefined로 출력됩니다.
console.log(maybe.length);
또한 any 타입은 타입의 일부만 알고, 전체는 알지 못할 때 유용하다.
- 예를 들어서 여러 타입이 섞인 배열을 받고자 할 때 유용하다.
let list: any[] = [1, true, "free"];
//any로 다루고 있기 때문에 index 1번째 요소가 boolean 타입이지만 number 타입으로 재할당할 수 있다.
list[1] = 100;
함수
TypeScript에서는 표준 JavaScript 함수가 작업을 수월하게 하도록 몇 가지 새로운 기능이 더 추가되어 있다.
- JavaScript에서 함수는 모든 애플리케이션의 기본적인 구성 요소이다.
- JavaScript에서의 함수와 마찬가지로 TypeScript에도 함수는 JavaScript와 마찬가지로 기명 함수(named function)와 화살표 함수(arrow function) 등으로 만들 수 있다.
- JavaScript에서 함수를 이렇게 작성했다.
//named function
function add(x, y){
return x + y;
}
//arrow function
let add = (x, y) => {
return x + y;
}
이를 TypeScript로 다시 표현해 보면 아래와 같다.
- TypeScript에서 함수를 표현할 때는 매개변수의 타입과 반환 타입을 명시해야 한다.
- 각 매개변수에 해당하는 타입을 작성한 뒤, 반환되는 타입을 괄호 뒤에 작성해준다다.
- 이는 타입 추론 기능을 활용하지 않는다고 가정했을 때 필수이다
- . 타입 추론 기능을 활용하지 않는다고 가정했을 때, 아래 함수는 반환값이 존재하는 함수이므로, 괄호 뒤에 콜론(:)을 붙이고 반환되는 타입을 기재한다.
- 여기서 반환되어야 하는 타입은 number여야 하므로, 콜론(:)을 붙이고 한 칸 띄운 다음 number를 작성한다.
//named function
function add(x: number, y: number):number {
return x + y;
}
//arrow function
let add = (x: number, y: number): number => {
return x + y;
}
만일 함수에 리턴값이 없다면, void를 사용하여 작성할 수 있다.
- 이 또한 타입 추론 기능을 활용하지 않는다고 가정했을 때 필수이다.
let printAnswer = (): void => {
console.log("YES");
}
또한 TypeScript는 JavaScript와 달리 매개변수의 개수에 맞춰 전달인자를 전달해야 한다.
let greeting = (firstName: string, lastName: string): string => {
return `hello, ${firstName} ${lastName}`;
}
//에러
greeting('coding');
//정상적으로 작동
greeting('coding', 'kim'); // hello, coding kim
//너무 많은 매개변수를 보내 에러
greeting('coding', 'kim', 'hacker');
만약 개발자가 전달인자를 전달하지 않거나, undefined를 전달했을 때 할당될 매개변수의 값을 정해놓을 수도 있다.
- 이는 JavaScript에서의 default parameter와 같은 동작을 한다.
- 뒤의 인자로 undefined를 보내도 값은 “hello, coding kim”으로 반환된다.
let greeting = (firstName: string, lastName="kim"): string => {
return `hello, ${firstName} ${lastName}`;
}
//정상적으로 작동
greeting('coding'); // hello, coding kim
//정상적으로 작동
greeting('coding', undefined); // hello, coding kim
//너무 많은 매개변수를 보내 에러
greeting('coding', 'kim', 'hacker');
- 혹은 선택적 매개변수를 원한다면 매개변수의 이름 끝에 ?를 붙임으로써 해결할 수도 있다.
- 그러나 이때는 greating('coding')과 같이 전달인자를 하나만 전달했기 때문에, 뒤의 매개변수는 undefined로 반환이 된다.
let greeting = (firstName: string, lastName?: string): string => {
return `hello, ${firstName} ${lastName}`;
}
//정상적으로 작동
greeting('coding'); // hello, coding undefined
//정상적으로 작동
greeting('coding', 'kim'); // hello, coding kim
//너무 많은 매개변수를 보내 에러
greeting('coding', 'kim', 'hacker');
TypeScript는 연산자를 이용해 타입을 정할 수 있다.
- JavaScript에서도 보았던 ||(OR) 연산자나 && (AND)와 같은 연산자를 이용하여 만들 수 있다.
- | 연산자를 이용한 타입을 유니온(Union) 타입이라고 하며, & 연산자를 이용한 타입은 인터섹션(Intersection) 타입이라고 부른다.
- JavaScript에서는 TypeScript에서처럼 유니온과 인터섹션 타입을 지원하지는 않는다.
유니온(Union) 타입
유니온 타입은 둘 이상의 타입을 합쳐서 만들어진 새로운 타입이다.
- | 연산자를 이용하며, 자바스크립트의 || (OR) 연산자와 같이 “A이거나 B이다”라는 의미의 타입이다.
- 예를 들어, number | string은 숫자 또는 문자열 타입을 의미한다.
아래의 코드는 value 매개변수의 타입을 any로 정의하고, 타입이 number인지 string인지에 따라 if-else 문으로 나누어 출력하고 있다.
- 그러나 any를 사용하는 것은 JavaScript로 작성하는 것과 큰 차이가 없기 때문에, 유니온 타입을 사용해 TypeScript의 이점을 살리면서 코딩하는 것이 좋다.
function printValue(value: any): void {
if (typeof value === "number") {
console.log(`The value is a number: ${value}`);
} else {
console.log(`The value is a string: ${value}`);
}
}
printValue(10); // The value is a number: 10
printValue("hello"); // The value is a string: hello
위의 printValue 함수는 숫자 또는 문자열 값을 입력받고 있다.
- 이때, 유니온 타입을 사용해 number | string 타입으로 지정하고 있다.
- 이후 입력된 값의 타입을 typeof 연산자를 사용하여 검사한 후, 해당 값이 숫자인 경우와 문자열인 경우 각각 다른 로그를 출력한다.
- 이처럼 유니온 타입은 다양한 타입의 값을 처리해야 하는 경우 유용하다.
function printValue(value: number|string): void {
if (typeof value === "number") {
console.log(`The value is a number: ${value}`);
} else {
console.log(`The value is a string: ${value}`);
}
}
printValue(10); // The value is a number: 10
printValue("hello"); // The value is a string: hello
유니온(Union) 타입의 장점
유니온 타입을 사용하면 타입을 추론할 수 있기 때문에, 타입에 관련된 API를 쉽게 자동완성으로 얻어낼 수 있다.
- 그러나 any 타입을 사용하면 타입을 추론할 수 없어, 자동완성 기능을 사용하기가 어렵다.
또한 코드의 가독성을 높일 수 있다.
- string | number | boolean 타입으로 선언된 변수는 문자열, 숫자, 불리언 타입 중 하나의 값을 가질 수 있다는 것이 명시적으로 표시되어 코드를 이해하기 쉽게 만들어 준다.
let value: string | number | boolean;
유니온(Union) 타입 사용 시 유의할 점
유니온 타입인 값이 있으면, 유니온에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있기 때문에 유의해야 한다.
- 아래와 같이 인터페이스를 사용하여 Developer와 Person을 정의했을 때를 가정해보자.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
실질적으로 askSomenone 함수 내부에서는 Developer와 Person이 갖고 있는 공통 프로퍼티인 name에만 접근할 수 있다.
- 왜냐하면 공통되고 보장된 프로퍼티만 제공해야 하기 때문이다.
- 만약 나머지 프로퍼티에도 접근하고 싶다면 타입 가드를 사용해야 한다.
function askSomeone(someone: Developer | Person) {
console.log(someone.name);
}
[타입 가드(Type Guard)란?]
TypeScript에서 타입을 보호하기 위해 사용되는 기능 중 하나이다. 타입 가드는 특정 코드 블록에서 타입의 범위를 제한해 해당 코드 블록 안에서 타입 안정성을 보장해 준다.
아래 코드는 타입 가드를 사용해 작성된 코드이다.
- TypeScript에서는 in 연산자를 제공하고 있다.
- in 연산자는 객체의 프로퍼티 이름과 함께 사용되며, 해당 프로퍼티가 객체 내에 존재하는지 여부를 검사한다.
function askSomeone(someone: Developer | Person) {
// in 연산자 : 타입스크립트에서 객체의 속성이 존재하는지를 체크하는 연산자
// in 연산자는 객체의 속성 이름과 함께 사용하여 해당 속성이 객체 내에 존재하는지 여부를 검사
if ('skill' in someone) {
console.log(someone.skill);
}
if ('age' in someone) {
console.log(someone.age);
}
}
인터섹션(Intersection) 타입
인터섹션(Intersection)은 둘 이상의 타입을 결합하여 새로운 타입을 만드는 방법이다.
- & 연산자를 사용하여 표현한다.
- 아래와 같이 타입을 결합해 사용할 수 있다.
- 여기서 value 변수는 string, number, boolean 타입을 전부 받을 수 있다.
let value: string & number & boolean;
인터섹션으로 타입을 연결해 하나의 단일 타입으로 표현할 수 있기 때문에, 타입 가드가 필요치 않다.
- 아래의 코드는 인터섹션 타입을 사용하여 Developer와 Person을 하나의 타입으로 묶었다.
- 따라서 askSomeone 함수 내에선 정의된 프로퍼티에 전부 접근할 수 있다.
- 인터섹션은 교집합이다.
- 따라서, Developer & Person은 Developer에 있는 속성과, Person에 있는 속성을 다 가지고 있다.
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
function askSomeone(someone: Developer & Person) {
console.log(someone.age);
console.log(someone.name);
console.log(someone.skill);
}
인터섹션 타입 (구조적 타입에서의 교집합) | 유니온 타입 (구조적 타입에서의 합집합) |
둘 이상의 타입을 결합해 새로운 합집합을 만들어 내는 것이기 때문에, 전달인자를 전달할 때 모든 프로퍼티를 전부 보내줘야만 한다. |
새로운 교집합을 만들어 내는 것이기 때문에, 유니온 타입은 전달인자를 전달할 때 선택지가 있다. |
인터섹션 타입은 타입 가드가 필요하지 않다. | 유니온 타입은 유니온 타입으로 정의한 둘 이상의 타입 내의 공통 프로퍼티에만 접근할 수 있어 타입 가드가 따로 필요하다. |
interface Developer {
name: string;
skill: string;
}
interface Person {
name: string;
age: number;
}
// 🟣 1. 유니온
function askSomeone(someone: Developer | Person) {
//이런 식으로 프로퍼티에 접근할 수 있다.
if ('skill' in someone) {
console.log(someone.skill);
}
if ('age' in someone) {
console.log(someone.age);
}
}
//유니온 타입은 전달인자를 전달할 때 선택지가 생긴다.
askSomeone({name: '김코딩', skill: '웹 개발'});
askSomeone({name: '김코딩', age: 20});
// 🟡 2. 인터섹션
function askSomeone2(someone: Developer & Person) {
//타입 가드를 사용하지 않아도 모든 프로퍼티에 접근할 수 있다.
console.log(someone.age);
console.log(someone.name);
console.log(someone.skill);
}
//그러나 인터섹션 타입으로 결합하게 된다면 전달인자를 전달할 때 선택지가 없다.
askSomeone2({name: '김코딩', skill: '웹 개발', age:20});
TypeScript로 포팅하기
JavaScript 코드들을 TypeScript로 포팅(*porting) 해보자.
- 여기서 포팅은 JavaScript 코드를 TypeScript 코드로 변환하는 작업을 의미한다.
[포팅(porting)]
어떤 시스템, 소프트웨어 또는 애플리케이션을 하드웨어나 소프트웨어 환경이 다른 시스템에서도 동작할 수 있도록 변경하는 과정
1. 옳게 바꾼 사례일까?
let shoes: Array<string, number> = ["Nike", 78, "Asics", 34];
- No!
- 제네릭 타입으로 배열 타입을 작성할 때, <> 안에는 하나의 인자만 들어갈 수 있다.
- 따라서 해당 코드는 알맞게 변환되지 않은 코드이다.
- 따라서 해당 코드는 다음과 같은 내용의 에러를 발생시킨다. Generic type 'Array<T>' requires 1 type argument(s).
2 옳게 바꾼 사례일까?
let person: {name: string, age: number} = {
name: "anna",
age: 15,
isDeveloper: false
}
- No!
- 객체 리터럴은 명시한 프로퍼티만 지정할 수 있다.
- 현재 명시된 프로퍼티는 name과 age이고, isDeveloper는 명시된 속성이 아니므로 에러가 발생한다.
3. Union
아래 에디터에서 코드를 작성한 뒤 터미널에 순서대로 아래와 같이 치면 결과를 확인해볼 수 있다.
1. tsc src/index.ts
2. node src/index.ts
tsc src/index.ts:
- TypeScript 컴파일러(tsc)를 사용하여 src/index.ts 파일을 컴파일한다.
- TypeScript 파일을 JavaScript로 변환하여 실행 가능한 형태로 만들어준다.
- 이 명령을 실행하면 TypeScript 코드가 JavaScript로 변환되고, 에러가 없는 경우 컴파일된 파일이 생성된다.
node src/index.js:
- Node.js 런타임 환경에서 src/index.js 파일을 실행한다.
교집합이 아닌 isMarried 와 isStudent 프로퍼티를 그냥 호출하게 되면, 에러가 뜬다.
아래는 TypeScript 코드가 JavaScript로 변환되고, 에러가 없는 경우 컴파일된 파일(오른쪽)이 생성된 경우이다.
- 아래의 터미널 창을 보면 결과도 정상적으로 출력이 된다.
4. Intersection
- 비교를 위해 3번과 비슷한 예시를 사용했다.
'FE > TypeScript' 카테고리의 다른 글
[TS] 제네릭 / 데코레이터 (0) | 2023.05.31 |
---|---|
[TS] JavaScript를 TypeScript로 포팅 하기 (0) | 2023.05.31 |
[TS] 타입 별칭 / 타입 추론 / 타입 단언 (0) | 2023.05.31 |
[TS] 열거형(Enum) / 인터페이스 / 클래스 / 상속 (0) | 2023.05.31 |
[TS] TypeScript 프로젝트 환경 구성하기 (1) | 2023.05.30 |