Originally posted by **MyeoungDev** July 25, 2023
# 📖 Node.js 백엔드 개발자 되기 부록 타입스크립트 입문부터 고급 기능까지
# A.1 타입스크립트 소개
**타입스크립트**는 **자바스크립트로 컴파일되는 언어**이다.
타입스크립트는 **자바스크립트에 타입을 추가한 언어**이다.
타입을 검사하는 **정적 타입 검사기**이면서, 타입스크립트의 컴파일의 결과가 자바스크립트로 컴파일되는 언어이기도 하다.
**확장자**로는 **.ts**를 사용한다.
## A.1.1 타입스크립트의 이점
- **컴파일 시점**에 **에러**를 **확일** 할 수 있다.
- **타입이 있기 때문**에 상대저으로 **더 많은 도움**을 받을 수 있다.
```java
function add(a, b) {
return a + b;
}
add(1, 2) // 3
add('1', '2') // 12
```
위 처럼 **타입이 없는 경우** 간단한 함수이지만 **개발자의 의도와 다르게 동작**할 수 있다.
```java
function add(a: number, b:number) {
return a + b;
}
add(3, 5) // 8
add('3', '5') // 컴파일 에
```
## A.1.3 TypeScript 사용해보기
```java
/* Node.js 설치가 되어 npm이 있다고 가정 */
/* TypeScript install */
$ npm install -g typescript@4.9.5
$ tsc --version
```
```java
/* TypeScript를 사용한 폴더의 terminal */
$ tsc --init
Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
```
이렇게 되면 해당 폴터에 `tsconfig.json` 파일이 생성된다.
```java
/* tsconfig.json file */
// ... 생략
"target" : "es2022" // 자바스크립트 언어 버전 변경
// ... 생략
```
`target` 옵션 변경 이유는 TypeScript의 기본 제공 타입 중 `bigInt` 는 ES2020 버전 이상에서 제공한다.
# A.2 타입스크립트 기초
## A.2.1 변수 선언
`var`, `let` , `const` 3가지 키워드로 변수를 선언할 수 있다.
```java
/* var type variable */
function foo() {
if (true) {
var a = 10;
}
return a;
}
console.log(foo());
// 결과
// 10
```
변수 `a` 를 `if` 문 안에 선언을 했는데 정상적으로 리턴된 이상한 모습이다.
이처럼 `var` 는 함수 영역 내의 어디서든 접근할 수 있다.
**쓰지말자. 잘못된 결과를 초래할 수 있다.**
```java
/* let type variable */
function bar() {
if (true) {
// const a = 10;
let a = 10;
}
return a;
}
console.log(bar());
// 변수 a가 없는 에러 발생.
```
`let` 과 `const` 두 개의 차이는 값 재할당이 가능하냐, 불가능 하냐 두 차이다.
- `let` : 값 재할당 가능
- `const`: 값 재할당 불가능
## A.2.2 타입 애너테이션
**타입 애너테이션(Type Annotations)** 는 **타입을 명시하는 방법**을 의미한다.
`var` , `let` , `const` 이후에 **<변수명>:<타입> 형식으로 변수를 선언.**
```java
/* 변수 선언 타입 지정 */
let username: String = "hofe";
let height: number = 180;
let isConditionGood: boolean = false;
```
```java
/* 함수에는 매게변수의 타입과 리턴타입을 지정할 수 있다.*/
function printMessage(message: string): void {
console.log(message);
}
```
```java
/* 객체 선언시 타입 지정 */
let myInfoWithGender: {
name : string;
height: number;
isConditionGood: boolean;
gender?: string; // 선택적 속성
} = {
name: "hofe",
height: 180,
isConditionGood: true,
};
```
여기서 `gender?: string` 의 경우 **변수명 뒤에 `?` 를 붙여**서 **선택적 속성(optional property)**로 만들 수 있다.
```java
/* 함수 매게변수의 선택적 속성 */
function printMessageWithAlert(message: string, isCritical?: boolean): void {
console.log(message);
if(isCritical) {
alert(message);
}
}
```
## A.2.3 기본 타입 7종
- `null`
- `undefined`
- `boolean`
- `string`
- `number`
- `bigint`
- `symbol`
```java
const one: number = 1;
const myName: string = "hofe";
const trueOrFalse: boolean = true;
const unIntended: undefined = undefined;
const nullable: null = null;
const bigNumber: bigint = 1231231241312451341341431313213123123n;
const symbolValue: symbol = Symbol("symbol");
```
### 타입스크립트 타입 계층도
![image](https://github.com/MyeoungDev/MyeoungDev/assets/73057935/b75546ed-1c71-456c-9b96-b311cb458013)
## A.2.4 배열과 튜플
타입스크립트에서 배열(Array)과 튜플(Tuple)은 **모두 여러 개의 요소를 담을 수 있는 데이터 구조**입니다.
그러나 배열과 튜플은 몇 가지 중요한 차이점이 있다.
1. **요소의 개수와 타입**:
- 배열(Array): 배열은 **요소의 개수와 타입이 가변적**이다. 즉, 배열에는 동일한 타입이 아닌 여러 가지 타입의 요소들이 포함될 수 있고, 요소의 개수를 동적으로 추가, 삭제, 변경할 수 있다.
- 튜플(Tuple): 튜플은 **요소의 개수와 타입이 고정**되어 있습니다. 튜플을 생성할 때 지정한 타입과 개수의 요소만 포함될 수 있으며, 더 많거나 적은 요소를 추가하거나 변경할 수 없다.
2. 표현 방법:
- 배열(Array): 배열은 **대괄호([])를 사용하여 생성**하고, 각 요소는 인덱스를 사용하여 접근한다.
- 튜플(Tuple): 튜플은 **대괄호([]) 내부에 타입과 요소의 개수를 명시하여 생성**하며, 각 요소는 인덱스를 사용하여 접근한다. 튜플의 각 요소는 정해진 순서대로 배치되어야 한다.
```java
/* TypeScript Array */
const numbers: number[] = [1, 2, 3, 4, 5]; // 숫자 배열
const stringArray: Array = ["a", "b", "c", "d", "e"]; // 문자열 원소
// 스프레드 연산자로 배열 합치기
const oneToTen = [...numbers, ...numbers2];
console.log(...oneToTen);
// 객체의 배열 타입
const idols: { name: string; brith: number } [] = [
{ name: "minji", birth: 2004 },
{ name: "hani", birth: 2005 },
{ name: "danielle", birth: 2006 },
{ name: "haerin", birth: 2004 },
{ name: "hyein", birth: 2005 },
];
// 배열의 원소가 객체인 타입
const gameConsoleArray: Array<{ name: string, launch: number }> = [
{ name: "플레이스테이션5", launch: 2020 },
{ name: "엑스 박스 시리즈 X/S", launch: 2020 },
{ name: "닌텐도 스위치", launch: 2017 },
{ name: "스팀덱", launch: 2021 },
];
```
```java
/* TypeScript Tuple */
// 튜플은 원소 개수만큼 타입 정의가 필요
const myTuple: [string, number] = ["hofe", 180];
// 튜플은 함수의 매개변수가 여러 개 일 때 유용
function printMyInfo(label: string, info: [string, number]): void {
console.log(`[${label}]`, ...info);
}
// 결과값 : [튜플 테스트] hofe 180
printMyInfo("튜플 테스트", myTuple);
// 튜플을 반환하는 함수
function fetchUser(): [string, number] {
return ["hofe", 180];
}
// 결과값을 분해해서 받을 수 있음
const [name24, height24] = fetchUser();
console.log(name24, height24);
```
## A.2.4 any, void, never
타입스크립트에서는 **타입을 지정하지 않을 경우 타입을 추론**한다.
```tsx
let anyValue = 10; // number 타입 추론
anyValue = "hello"; // 컴파일 에러!
anyValue = true; // 컴파일 에러!
```
### any
`any` 는 **`unknown` 을 제외하고 가장 상위에 있는 타입**이다.
**타입을 모르거나 지정할 수 없을 때 사용.**
```tsx
let anyValue: any = 10;
anyValue = "hello";
anyValue = true;
// 컴파일 에러 발생하지 않음.
```
단, 이 경우는 **런타임 시점에 에러가 날 가능성을 높이므로 지양해야 한다.**
### void
**리턴값이 없음을 의미.**
```tsx
function print(value: any): void {
console.log(value);
}
```
### never
**의도적으로 값을 반환하지 않는 때 사용**
예외를 던지는 함수이거나 무한 루프를 실행하는 함수에 사용
```tsx
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while(true){}
}
```
## A.2.6 유니온 타입과 내로잉
유니온 타입을 사용하면 **변수 하나를** **여러 가지 타입으로 지정**할 수 있다.
```tsx
let anyValue: number | string | boolean = 10;
anyValue = "hello";
anyValue = true;
```
타입의 범위를 좁히는 데 사용하는 검사 방법을 타입 가드(Type Guard)라고 한다.
값 할당 또는 조건문으로 검사해 타입을 좁힌다.
연산자로는 `typeof` , `instanceof`, `in` 이 있다.
```tsx
let anyValue : number | string | boolean = 10;
printAny(anyValue);
anyValue = "hello";
printAny(anyValue);
anyValue = true;
printAny(anyValue);
function printAny(value : number | string | boolean): void {
if (typeof value === "number") {
console.log(value.toExponential(3));
} else if (typeof value === "string") {
console.log(value.toUpperCase());
} else if (typeof value === "boolean") {
console.log(value ? "참" : "거짓");
}
}
// 10
// HELLO
// 참
```
## A.2.7 타입 별칭
타입에 별칭을 지정하여 사용할 수 있다.
```tsx
type nsb = number | string | boolean;
type nullableNsb = nsb | null | undefined;
// type nullableNsb = nsb | null;
let anyValue: nsb = 10;
let nullableAnyValue: nullableNsb = null;
```
## A.2.8 인터섹션 타입
인터섹션 타입은 타입 A가 있고 타입 B가 있다면 A이면서 B인 타입을 정의한다.
```tsx
type cup = {
size: string;
};
type brand = {
brandName: string;
}
type brandedCup = cup & brand;
let starbucksGrandeSizeCup: brandedCup = {
brandName: "스타벅스",
size: "grande",
};
```
## A.2.9 리터럴 타입
**기본 타입의 값들을 조합**해서 **한정적인 값들만 나타내는 타입**을 만들 수 있는 것을 리터럴 타입이라고 한다.
```tsx
type CoffeeSize = "small" | "medium" | "large";
let myCoffeSize: CoffeeSize = "small";
let starbucksCoffeeSize: CoffeeSize = "tall"; // 타입 에러!
```
## A.2.10 함수 타입
```tsx
function echo(message: string): string {
console.log(message);
return message;
}
type FuncEcho = (message: string) => string;
const funcEcho2: FuncEcho = echo;
```
위와 같이 함수에 대한 **매개변수 타입과 리턴타입을 지정한 타입**을 만들어서 사용할 수 있다.
# A.3 인터페이스와 클래스
인터페이스 타입을 선언하는 문법은 객체 타입의 별칭을 만드는 문법과 비슷하다.
인터페이스는 type을 사용한 객체의 별칭 타입과 비교해 더 읽기 쉬운 오류 메시지, 더 빠른 컴파일러 성능, 클래스와 함께 사용할 수 있는 장점을 제공한다.
```tsx
type BookType = {
title: string;
price: number;
author: string;
}
interface Book {
title: string;
price: number;
author: string;
}
let bookType: BookType = {
title: "백엔드 개발자 되기",
price: 10000,
author: "박승규",
};
let book: Book = {
title: "백엔드 개발자 되기",
price: 10000,
author: "박승규",
};
```
## A.3.2 인터페이스의 선택적 속성과 읽기 전용 속성
인터페이스도 선택적 속성을 사용할 수 있다.
```tsx
/* 인터페이스 선택적 속성 */
interface Car {
name: string.
price: number,
brand: string,
options?: string[]; // 선택적 속성
}
let avante: Car = {
name: "아반떼",
price: 1500,
brand: "현대",
options: ["에어컨", "네비게이션"]
};
let morning: Car = {
name: "모닝",
price: 650,
brand: "기아",
};
```
```tsx
/* 인터페이스 읽기 전용 속성 */
interface Citizen {
id: string;
name: string;
region: string;
readonly age: number;
}
let hofe: Citizen = {
id: "123415",
name: "hofe",
region: "Busan",
age: 40
}
hofe.age = 20; // age 속성은 읽기 전용이므로 에러!
```
## A.3.3 인터페이스 확장하기
```tsx
interface WebtoonCommon {
title: string,
createdDate: Date,
updateDate: Date;
}
interface Episode extends WebtoonCommon {
episodeNumber: number;
seriesNumber: number;
}
interface Series extends WebtoonCommon {
sereisNumber: number;
author: string;
}
const episode: Episode = {
title: "나 혼자만 레벨업 1화",
createdDate: new Date(),
updateDate: new Date(),
episodeNumber: 1,
seriesNumber: 123,
};
const series: Series = {
title: "나 혼자만 레벨업",
createdDate: new Date(),
updateDate: new Date(),
seriesNumber: 123,
author: "나작가"
}
```
## A.3.4 인터페이스 병합
```tsx
interface Clock {
time: Date;
}
interface Clock {
brand: string;
}
interface Clock {
price: number;
}
const worngClock: Clock = { // brand, price 속성이 없어서 에러
time: new Date(),
};
const clock: Clock = {
time: new Date(),
brand: "롤렉스",
price: 100000000
}
```
## A.3.5 클래스의 메서드와 속성
```tsx
class Rectangle {
width: number;
height: number;
constructor(width: number height: number) {
this.width = width;
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
const rectangle = new Rectangle(10, 5);
rectangle.getArea();
```
## A.3.6 인터페이스를 구현한 클래스
클래스가 인터페이스를 구현하도록 하면 클래스에 반드시 선언해야 하는 **속성과 메서드를 강제**할 수 있다.
```tsx
inteface IClicker {
count: number;
click(): number;
}
class Clicker implements IClicker {
count: number = 0;
click(): number {
this.count += 1;
console.log(`Click! [count] : ${this.count}`);
return this.count;
}
}
const clicker = new Clicker();
clicker.click();
```
## A.3.7 추상 클래스
```tsx
abstract class Logger {
prepare() {
console.log("log ready");
}
log(message: string) {
this.prepare();
this.execute(message);
this.complete();
};
abstract execute(message: string): void;
complete() {
console.log("log end");
}
}
class FileLogger extends Logger {
filename: string;
constructor(filename: string) {
super();
this.filename = filename;
}
execute(message: string): void {
// 생략
}
```
## A.3.8 클래스의 접근 제어자
- public : 모든 곳 접근 가능(기본)
- protected: 클래스 내부 혹은 자녀
- private: 클래스 내부만
```tsx
class Parent {
openInfo = "공개 정보"; // 다른 객체 접근 가능
protected lagacy = "유산"; // 자신, 자식 클래스만 접근 가능
private parentSecret = "부모의 비밀 정보"; // 해당 클래스에서만 접근 가능
// ...
}
class Child extends Parent {
private secret = "자녀 비밀 정보"; // 해당 클래스만 접근 가능
// 상속한 Parent 클래스의 lagacy 접근 가능
// ...
}
```
# A.4 타입스크립트의 고급 기능
제네릭과 맵드 타입, 데코레이터 이런게 있다.
## A.4.1 제네릭 함수
```tsx
function genericEcho(message: T): T {
console.log(message);
return message;
}
genericEcho(1);
genericEcho("hello");
genericEcho(myPhone); // any를 쓸꺼면 제네릭 쓸 필요 없다.
```
## A.4.2 제네릭 인터페이스
```tsx
interface ILable {
label: Type;
}
const stringLabel: ILabel = {
label: "Hello"
}
const numberLabel: ILabel = {
label: 100
}
```
## A.4.3 제네릭 제약 조건
```tsx
intercae ICheckLength {
length: number;
}
function echoWithLength(message: T) {
console.log(message);
}
echoWithLength("Hello");
echoWithLength([1,2,3]);
echoWithLength({length: 10});
echoWithLength(10) // 10 이라는 숫자에는 length가 없기 때문에 에러가 난다.
```
## A.4.4 데코레이터
아직 정식기능은 아니라고 한다.
`@데코레이터명` 의 형식을 사용해 데코레이터가 적용된 클래스, 메서드, 속성, 매개변수의 정보를 읽어서 동작을 변경할 수 있는 메타 프래그래밍을 지원하는 기능이다.
2023년 2월 기준 타입스크립트에 사용하려면 `tsconfig.json` 에 `“experimentalDecorators”: true` 설정을 추가해야 한다.
문법도 어렵고 처음 접하는 저에게는 난해하고 아직 정식 출시도 아니기에 넘어가겠습니다.
## A.4.5 맵드 타입
맵드 타입은 기존의 타입으로 새로운 타입을 만들어내는 문법이다.
타입스크립트에서는 같은 타입인데 기존 속성을 선택 속성으로 변경하거나, 읽기 전용으로 변경하는 등 자주 사용하는 로직을 유틸리티 타입으로 제공한다.
Discussed in https://github.com/FeGwan-Training/FeGwan/discussions/40