FeGwan-Training / FeGwan

0 stars 0 forks source link

[TypeScript] Node.js 백엔드 개발자 되기 Chapter A 타입스크립트 입문부터 고급 기능까지 #41

Open MyeoungDev opened 1 year ago

MyeoungDev commented 1 year ago

Discussed in https://github.com/FeGwan-Training/FeGwan/discussions/40

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 맵드 타입 맵드 타입은 기존의 타입으로 새로운 타입을 만들어내는 문법이다. 타입스크립트에서는 같은 타입인데 기존 속성을 선택 속성으로 변경하거나, 읽기 전용으로 변경하는 등 자주 사용하는 로직을 유틸리티 타입으로 제공한다.