섹션1. TypeScript 시작하기
1) TypeScript란?
타입스크린트는 결국 자바스크립트를 기반으로 하는 프로그래밍 언어이다.
자바스크립트 언어에 새로운 기능과 장점을 추가해 코드를 더 쉽고 강력하게 작성할 수 있다.
(1) 핵심 기능
타입을 추가한다.
브라우저 런타임에서 에러가 발생하기 전에 코드의 에러를 개발자로서 미리 식별할 기회를 갖게 된다.
타입스크립트의 몇가지 새로운 기능을 통해 작업을 보다 나은 방법을 수행할 수 있는 것만 아니라, 오류 검사가 가능하다.
(2) 특이사항
타입스크립트는 브라우저와 같은 자바스크립트 환경에서 실행할 수 없다.
브라우저는 타입스크립트를 파싱할 수 없다. 그렇다면 어떻게 브라우저에 적용되는 것일까.
타입스크립트는 언어이자 도구이다. 코드를 실행하여 타입스크립트 코드를 자바스크립트로 컴파일한다. 즉, 새로운 기능과 장점을 갖춘 타입스크립트 코드로 작성하면 자바스크립트로 컴파일 된다.
그래서 타입스크립트의 새로운 기능들을 활용한 코드들은 개발도중 코드를 컴파일할때만 실행할 수 있는 코드이다.
(3) 프리뷰
자바스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const button = document.querySelector("button");
const input1 = document.getElementById("num1")
const input2 = document.getElementById("num2")
function add(num1, num2) {
if (typeof num1 === "number" && typeof num2 === "number"){
return num1 + num2;
} else {
return +num1 + +num2;
}
}
button.addEventListener("click", function() {
console.log(add(+input1.value, +input2.value));
});
자바스크립트에서는 원치않는 연산을 하기 위해서는 전달된 인자가 숫자인지 typeof
연산자를 통해 타입을 확인해야 한다. 만약, 문자열이 인자로 들어올 경우 "5" + "3" = "53"
과 같은 연산이 나오기때문에 원치않는 결과가 도출될 것이다. 그러나, 타입스크립트에서는 함수의 전달되는 인자의 타입을 숫자로 확정함으로써, 위와 같은 사태를 막을 수 있다.
타입스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// !는 타입스크립트에게 이것이 결코 null을 가져오지 않을 것임을 알려준다.
// input이라고 알려줘야 하는 이유는 value라는 속성은 html의 모든 태그에 있지 않다. input태그란 것을 알려줘야 추후 value속성을 사용할 수 있는 것이다.
const button = document.querySelector("button");
const input1 = document.getElementById("num1")! as HTMLInputElement;
const input2 = document.getElementById("num2")! as HTMLInputElement;
// num1과 num2가 숫자가 될 것 이라고 타입스크립트에 말해준다.
function add(num1: number , num2: number) {
return num1 + num2;
}
button.addEventListener("click", function() {
console.log(add(+input1.value, +input2.value));
});
// 컴파일 하는 방법 => tsc 파일 이름
이렇게 타입을 명확하게 전달하여, 타입스크립트에게 개발자의 의도를 확실하게 전달할 수 있다. 이렇게 작성한 코드는 tsc 파일이름
명령어를 통해 자바스크립트 파일로 컴파일 할 수 있다.
2) 간단한 개발 서버로 프로젝트 시작하기
1
2
3
4
5
#프로젝트 활성화
$ npm init
# lite-server:package.josn파일 옆에 항상 index.html 파일을 제공하는 간단한 개발 서버이다.
$ npm install --save-dev lite-server
--save-dev
: 이 프로젝트에서 사용할 수 있는 도구를 설치하도록한다. (개발전용 종속성으로 표시한다.)- 이는 개발 도중 도움이 될 도구로 메인코드의 일부로 실행되는 코드를 포함하지 않는다.
- lite-server: Node.js 기반의 경량 웹서버로 html 또는 javascript 변경을 감지하고 socket을 이용하여 css 변경을 주입하는 등의 유용한 기능을 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// package.json
{
"name": "ts",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "lite-server"
},
"author": "",
"license": "ISC",
"devDependencies": {
"lite-server": "^2.6.1"
}
}
위와 같이 package.json파일을 수정한다. start부분에 lite-server를 기입함으로써, 현재 node.js 프로젝트를 npm을 통해 구동할 시 실행될 명령어를 입력한다.
1
2
# npm 구동하면 자동적으로 lite-server가 구동되면서 자동적으로 index.html이 웹브라우저에 로딩된다.
$ npm start
섹션2. TypeScript 기본 & 기본 타입
핵심 타입스크립트 문법에 대해 알아보자
1) 개요
타입스크립트는 자바스크립트보다 많은 타입을 가지고 있다. 먼저, 자바스크립트에 공통으로 있는 타입들을 정리했다.
Types | Desc | Example |
---|---|---|
number | 숫자형 (모든 숫자) | 1 , 5.3 , -10 |
string | 문자열 | Hi , "Hi" |
boolean | 참, 거짓 | true , false |
object | 객체 | {age: 30} |
Array | 배열 | [1, 2, 3] |
Function | 함수 | function(){} |
아래의 타임들은 자바스크립트에는 없지만, 타입스크립트에 있는 타입들이다.
Types | Desc | Example |
---|---|---|
Tuple | 길이와 타입이 고정된 배열 | [1, 2] |
Enum | 라벨이 있는 열거 목록 | enum {NEW, OLD} |
Union | 여러 타입을 적용 | number | stirng |
Literal | 정확한 값을 가지는 타입 | "as-number" |
Alias |
2) number, string, boolean
프리뷰에서 봤던 add함수를 예시를 들어서 다시 보자. 프리뷰에서는 add함수에서 숫자가 아닌 값을 인자로 넣는다면 숫자로 변경하고 +연산을 했다. 그러나 좀 더 간단하게, 자바스크립트로 다음과 같이 처리를 할 수도 있다.
자바스크립트
1
2
3
4
5
6
7
8
9
10
11
12
function add(n1, n2) {
if (typeof n1 !== "number" || typeof n2 !== "number"){
throw new Error("Incorrect input!")
}
return n1 + n2;
}
const number1 = "5";
const number2 = 2.8;
const result = add(number1, number2);
console.log(result);
위의 프리뷰와 달리 숫자가 아니면 타입을 인자로 넣으면 에러를 발생시켰다. 사실 타입스크립트를 사용하면 이를 애초에 막을 수 있는데, 발생시킬 필요가 없는 에러를 억지로 발생시킨 꼴이 되었다.
자바스크립트의 경우 타입은 런타임 중에 확인된다. 그래서 특정 타입에 의존하는 코드가 있을 경우, 런타임에서 현재의 타입을 확인할 수 있게 도와주는 typeof
연산자를 사용하게 된다.
하지만, 타입스크립트는 정적타입으로 개발 도중에 끝나는 변수와 매개변수의 타입을 정의하게 된다. 즉, 타입스크립트의 타입은 컴파일 중에 확인되어 런타임 도중에 갑자기 타입이 변경되지 않는다.
타입스크립트
1
2
3
4
5
6
7
8
9
function add(n1: number, n2: number) {
return n1 + n2;
}
const number1 = "5";
const number2 = 2.8;
const result = add(number1, number2);
console.log(result);
이처럼 타입스크립트는 이처럼 컴파일 도중에 잘못 사용하고 있는지 확인하고 에러를 통해 알려준다. 아주 굿!
3) 객체
객체의 타입은 어딘가에 사용되는 객체 타입을 설명하기 위해 작성되는 것으로 key-value쌍을 통해 작성해야 한다. 만약, 상세히 객체의 타입을 적으려면 다음과 같이 작성해야 한다.
1
2
3
4
5
6
7
8
9
const person: {
name: string;
age: number;
} = {
name: "Coco",
age: 30
}
console.log(person.name);
하지만, 이방식은 추천하지 않는다. 그냥 기존에 쓰던 방식으로 객체를 작성해도 타입스크립트가 올바른 타입을 추론할 수 있기 때문에 다음과 같은 코드가 좋다.
1
2
3
4
5
6
const person = {
name: "Coco",
age: 30
}
console.log(person.name);
4) 배열
배열의 경우 어떤 타입을 넣을지 선택할 수 있다.
- 타입이 고정된 경우:
number[]
,string[]
등 - 타입이 혼합된 경우:
any[]
만약, 이미 배열에 값이 있으면 알아서 타입스크립트가 타입을 추론한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const person = {
name: "Coco",
age: 30,
// hobbies가 문자열 배열인걸로 알아서 추론한다.
hobbies: ["Sports", "Cooking"],
}
// 문자열 배열
let favoriteActivities: string[];
favoriteActivities = ["Sports"];
// 혼합 배열
let favoriteActivities2: any[];
favoriteActivities2 = ["Sports", 2];
console.log(person.name);
// person.hobbies가 문자열 배열으로 추론하기 때문에 hobby는 문자열로 인식할 수 있기 때문에 toUpperCase()를 사용할 수 있다.
// 우리가 설정한 타입때문에 hobby가 문자인것을 알 수 있다.
for (const hobby of person.hobbies) {
console.log(hobby.toUpperCase());
// console.log(hobby.map()) // !!!ERROR!!!!
}
5) 튜플
타입스크립트는 배열의 값에 어떤 타입이 있는지 알지만, 구성을 알지 못한다. 이럴 때 튜플을 사용하게 된다. 즉, 타입은 알지만 구성은 모르게 때문에 명시적으로 재정의 해야 한다. 튜플을 타입스크립트에만 존재하는 타입이기 때문에 자바스크립트에서는 일반 배열로 인식된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const person: {
name: string;
age: number;
// 배열 타입 지정
hobbies: string[];
// 튜플 타입 지정
role: [number, string];
} = {
name: "Coco",
age: 30,
hobbies: ["Sports", "Cooking"],
// 타입스크립트는 배열이 문자열과 숫자 유형의 타입으로 구성된 것은 알지만, 첫번째 요소가 숫자고 두번째 요소가 문자열이여야 한다는 것은 모른다.
// 명시적으로 재정의 한다.
role: [2, 'author']
}
// 오류 발생
person.role = [0, 'admin', 'user'];
// 오류 발생
person.role[1] = 10;
// 배열의 길이를 지정하지 않았기 때문에 push를 통해 추가할 수 있다.
person.role.push("admin");
6) ENUM
열거형타입으로 이름이 있는 상수들의 집합을 정의할 수 있다. 열거형을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 만들 수 있다. 만약 라벨이 필요한 경우 자바스크립트에서는 다음과 코드를 만들 수 있다.
자바스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const ADMIN = 0;
const READ_ONLY = 1;
const AUTHOR = 2;
const person = {
name: "Coco",
age: 30,
hobbies: ["Sports", "Cooking"],
role: ADMIN
}
if (person.role === ADMIN) {
console.log("is admin")
}
타입스크립트로 다음과 같이 수정할 수 있다.
타입스크립트
1
2
3
4
5
6
7
8
9
10
11
12
13
// ADMIN은 1, READ_ONLY은 2, AUTHOR은 3을 값으로 가진다.
enum Role { ADMIN, READ_ONLY, AUTHOR};
const person = {
name: "Coco",
age: 30,
hobbies: ["Sports", "Cooking"],
role: Role.ADMIN
}
if (person.role === Role.ADMIN) {
console.log("is admin")
}
아래의 이미지처럼 자동으로 숫자가 라벨링처럼 생기는 것을 확인할 수 있다.
타입스크립트를 자바스크립트로 컴파일한 결과
1
2
3
4
5
6
7
var Role;
(function (Role) {
Role[Role["ADMIN"] = 0] = "ADMIN";
Role[Role["READ_ONLY"] = 1] = "READ_ONLY";
Role[Role["AUTHOR"] = 2] = "AUTHOR";
})(Role || (Role = {}));
;
7) Union, Literal, Alias
Union 타입은 입력값을 좀 더 유연하게 처리할 수 있게 해준다. 여러 타입을 함수의 매개변수, 상수 혹은 변수로 사용해야 한다면 유니언 타입을 사용할 수 있다. 만약, Union타입을 통해 함수의 매개변수를 유연하게 한다면 타입에 따라 함수 로직이 달라질 수 있기 때문에 종종 런타임 검사가 필요한 경우도 있다.
Literal 타입은 숫자나 문자열과 같은 타입이 아니라 정확한 값을 가지는 타입이다.
Alias는 type
키워드를 활용하여 타입을 변수에 지정할 수 있다. type 변수이름 = 인코딩하고자 하는 타입
과 같이 설정하면된다. Alias는 Union타입을 자주쓰기 귀찮을 때 사용하는 경우가 많다. 즉, 재사용 가능한 이름을 지정하게 되는 것이다. 또한, 유니온 타입을 저장하는 것만 가능한 것이 아니라 복잡할 수 있는 객체 타입에도 별칭을 붙일 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Alias
type Combinable = number | string; // Union
type ConversionDescriptor = "as-number" | "as-text" // Literal타입으로 Union타입을 생성
function combine( input1: Combinable, input2: Combinable,resultConversion: ConversionDescriptor) {
let result;
// 매개변수가 유니언 타입이기 때문에 런타임 검사가 필요한 경우가 있다.
if (typeof input1 === "number" && typeof input2 === "number" || resultConversion === "as-number" ) {
result = +input1 + +input2;
} else {
result = input1.toString() + input2.toString();
}
return result
}
const combineAges = combine(30, 26, "as-number");
console.log(combineAges);
const combineStringAges = combine("30", "26", "as-number");
console.log(combineStringAges);
const combineNames = combine("MAX", "Anna", "as-text");
console.log(combineNames);
복잡한 객체타입을 Alias를 통해 별칭을 지정할 수도 있다. 타입 별칭을 사용하면 불필요한 반복을 피하고 타입을 중심에서 관리할 수 있습니다. 아래는 Alias를 사용하지 않은 경우이다.
1
2
3
4
5
6
7
8
9
type User = { name: string; age: number };
function greet(user: { name: string; age: number }) {
console.log('Hi, I am ' + user.name);
}
function isOlder(user: { name: string; age: number }, checkAge: number) {
return checkAge > user.age;
}
코드를 아래와 같이 단순화할 수 있다.
1
2
3
4
5
6
7
8
9
type User = { name: string; age: number };
function greet(user: User) {
console.log('Hi, I am ' + user.name);
}
function isOlder(user: User, checkAge: number) {
return checkAge > user.age;
}
8) Function
함수에 매개변수와 반환값에 타입을 지정할 수 있다. 매개변수에 따라 원치않는 결과가 나올 수 있기 떄문에 타입을 명시적으로 지정하는 것이 좋다.
또한, 함수에는 반환 타입도 중요하다. 반환타입은 타입스크립트가 추론한다. 반환타입을 명시적으로 기입할 수 있지만 추론과 같은 타입을 적어야 하기 때문에 굳이 명시적으로 적을 필요가 없다. 만약, 아무것도 반환하지 않는경우 반환타입은 void
가 된다.
콜백함수도 마찬가지이다. 함수로 타입을 지정하고 매개변수와 반환값에 타입을 지정할 수 있다. 어떤 유형의 함수를 지정할지 구체화 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function add(n1: number, n2: number): number {
return n1 + n2;
}
// 아무것도 반환하지 않는 경우: 반환타입은 void타입이 된다.
function printResult(num: number): void {
console.log("Result: " + num);
}
// 아무것도 반환하지 않는 함수의 반환값을 사용하면 undefined가 출력된다.
console.log(printResult(add(5, 12)));
// undefined는 반환은 하지만, 실제 값을 반환하지 않을 때 사용할 수 있다.
function printResult2(num: number): undefined {
console.log("Result: " + num);
return;
}
let someValue: undefined;
let combineValues: (a: number, b: number) => number;
// function타입만 지정해서는 잘못된 함수를 지정할 수 없다. 그러나 매개변수와 반환값에 타입을 지정해야 한다.
combineValues = printResult;
// 콜백함수 정의
function addAndHandle(n1: number, n2: number, cb: (num: number) => void) {
const result = n1 + n2;
cb(result);
}
addAndHandle(10, 20, (result) => {
console.log(result);
});
스프레드 연산자
만약 함수에 스프레드 연산자를 통해 매개변수를 전달한다면, 이 또한 타입을 설정해야 한다.
1
2
3
4
5
6
7
8
const add = (...numbers: number[]) => {
return numbers.reduce((curResult, curValue)=> {
return curResult + curValue;
}, 0);
};
const addNumbers = add(5, 10, 2, 3.7);
console.log(addNumbers);
이것이 인수를 무한정으로 받아들이기 위한 정말 유용한 기능인 나머지 매개변수이다.
꼭 배열이 아니라 튜플 형식으로도 가능하다. ...numbers: [number, number, number]
9) unknown
개발자는 종종 알지 못하는 변수 유형을 설명해야 할 수도 있습니다. 이러한 경우, 이 변수가 무엇이든 될 수 있음을 알려주는 타입을 제공하기 위해 unknown
유형을 사용할 수 있다. typeof
검사, 비교 검사를 통해 타입을 보다 구체적인 것으로 좁힐 수 있다.
any 는 타입을 좁혀서 사용하지 않아도 되지만, unknown 은 타입을 좁혀서 사용해야만 하기 때문에 any 를 사용하는 곳에서 unknown 을 사용하여 보다 안전하게 코딩이 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 알수 없는 타입: 어떤 타입을 저장할지 모른다.
let userInput: unknown;
let userName: string;
// any와 같이 어떤 타입이든 올 수 있다.
userInput = 5;
userInput = "Coco";
// 에러 발생: unknown 타입은 any 타입을 제외한 다른 타입으로 선언한 변수에 할당할 수 없다.
username = userInput
//추가적인 검사를 추가하여 어떤 작업을 수행할지 명시할 수 있다.
if (typeof userInput === "string") {
userName = userInput;
}
10) never
일반적으로 함수의 리턴타입으로 사용되며 항상 오류를 출력하거나 리턴 값을 절대로 내보내지 않음을 의미한다. 즉, never는 함수가 종료하지 않아 절대 return 하지 않을 때 사용된다. (void는 return 값이 없을 뿐이지 함수는 종료한다. )
never의 사용은 코드 품질의 관점에서 의도를 더 분명하게 할 수 있다. never를 반환 값으로 갖는 함수는 아무것도 반환하지 않고 기본적으로 스크립트나 스크립트의 일부를 충돌시키거나 망가트리기 위한 것임을 코드를 읽는 개발자가 알게 한다.
1
2
3
4
5
6
7
function generateError(message: string, code: number):never {
throw { message: message, errorCode: code };
}
const result = generateError("An error occurred", 500);
console.log(result);