[Typescript] 정리

먼지·2022년 3월 12일
1

TypeScript

목록 보기
1/3
post-thumbnail

땅콩코딩 타입스크립트

타입스크립트란 무엇일까?

자바스크립트만 사용하는 것보다 버그를 줄이고 -> 쉬운 유지 보수 -> 질 좋은 코드
TypeScript is Superset of JavaScript
새언어가 아닌 자바스크립트를 기반으로한 언어!

JS의 모든 기능과 존재하지 않는 새로운 기능도 포함함. 좀 더 나은 버전의 JS

특징 몇가지

  • 타입 표기 - 변수 값에 데이터 타입 지정 가능
  • 객체지향적 - set, constructor, private 등 객체지향의 프로그래밍 특성을 지원함
  • 컴파일 타임 오류
    - 컴파일시에 나타나는 에러. 코드가 실행되기 전에 컴파일 에러메시지를 받게됨
    - TS는 JS 환경에서 실행될 수 없음 브라우저가 이해 X 따라서 TS -> JS 코드로 컴파일 되어야 함. (컴파일이란 어떤 언어의 코드를 다른 언어로 바꿔주는 변환 과정)

타입스크립트란 프로그래밍 언어인 동시에 컴파일러!

타입스크립트 개발 환경 설정

  1. IDE 코드에디터(vscode) 설치
    https://code.visualstudio.com/

  2. 익스텐션 설치하기 - 비쥬얼 스튜디오 코드에 Third party plugins 설치하기
    prettier, eslint(코드품질도구), path intellisense, bracket pair colorizer, meterial icon theme 등 확장 프로그램

  3. 타입스크립트 설치

// window
npm install -g typescript

// mac
sudo npm install -g typescript

NPM(Node Package Manager)은 Node JS의 한 부분으로 npm을 사용하려면 nodejs를 설치해야함

  1. Build Your First Typescript File
    다시! 타입스크립트는 JS를 기반으로 한 언어 JS에서 유효한 코드는 TS에서도 유효함!
// app.ts
function logName(name: string) {
  console.log(name);
}
logName('Jack');

컴파일 명령어

// tsc = typescript compiler
tsc app.ts

typescript 코드가 javascript 코드로 변환됨!

// app.js
function logName(name) {
    console.log(name);
}
logName('Jack');
node app.js
> Jack

컴파일된 자바스크립트 파일 연결

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="app.js" defer></script>
</head>
<body>
  
</body>
</html>

ts --init 여러 설정옵션이 있는 tsconfig.json 파일 생성

tsc -w watch 감시모드. tsc가 파일 내 변경사항을 감시하고 변경내용을 자동으로 다시 컴파일해줌

타입 추론 (Type Inference)

타입스크립트의 가장 독특한 특징은 Static Typing(정적 타이핑)을 js에 제공하는 것임. 타입 추론(Type Inference)과 타입 명시(Type Annotations)를 배우면 이해할 수 있을 것!

// app.js
let a = 5; // 변수 초기화
a = 'hello'; // 에러 x

재할당하려는 변수값이 숫자가 아니기 때문에 a 변수에 할당될 수 없다.

// app.ts
let a = 5;
a = 'hello'; // 빨간줄! Type '"hello"' is not assignable to type 'number'.

이런 현상은 타입 추론 때문에 일어나는 것.
=> 타입스크립트에서는 타입 표기가 없는 경우 우리의 코드를 읽고 분석하여 타입을 유추해낼 수 있음!

이미 할당값을 바탕으로 타입추론

// app.ts
let student = {
  name: 'Jake',
  course: 'Getting Started with TypeScript',
  codingIQ: 80,
  code: function () {
    console.log('brain is working haed')
  }
};

student.name = 10; // error

타입 명시 (Type Annotations)

변수를 선언할 때, 변수 값의 타입을 명시함으로써 변수 값의 데이터 타입을 지정!

Syntax

let x: string = '나는 영원한 문자열';

함수의 인자와 반환되는 값의 타입을 명시. 함수가 아무것도 반환하지 않는다면 void로 의미 부여.

function getStudentDetails(id: number): void {}

컴파일 에러 - 선언된 형식이 'void'도 'any'도 아닌 함수는 반드시 값을 반환해야 합니다.

function getStudentDetails(id: number): object {}

타입스크립트에게 타입에 관한 더 많은 정보를 제공될 수록 좋음!! 단순히 객체라 지정하는 것보다 세부적이고 자세하게 타입을 명시하기 - 실제로 반환하는 객체의 구조

function getStudentDetails(studentID: number): {
  studentID: number;
  studentName: string;
  ...
  createDate: Date;
} {
  return null;
}

타입으로 사용되는 인터페이스 (Interface)

객체의 구조를 인터페이스로 어떻게 정의하고 사용하는지?!

복잡해보이는 코드를 간결하게

function getStudentDetails(studentID: number): {
  studentID: number;
  studentName: string;
  age: number;
  ...
} {

대문자로 시작하고 TS에선 이름 앞에 I를 붙이지 않는 것을 권장함
타입스크립트 네이밍 컨벤션 문서

interface Student {
  studentID: number;
  studentName: string;
  age: number;
  ...
}

// 완성된 인터페이스는 타입으로써 사용 가능!
function getStudentDetails(studentID: number): Student {
  return ...
}

인터페이스를 타입으로 가지는 값은 인터페이스의 구조를 그 값으로 가지도록 강제됨. getStudentDetails 반환값은 반드시 Student 인터페이스에 정의된 프로퍼티들을 가져야 한다는 뜻

인터페이스를 조금 더 유연하게 사용하려면? 만약 age 프로퍼티가 있어도 되고 없어도 되도록 나타낼 수 있음!

// optional ?
interface Student {
  studentID: number;
  studentName: string;
  age?: number;
  ...
}

코드의 재사용성을 높여줌. 반복되는 코드를 피하는 것은 좋은 코드를 위한 노력 중 하나!

function saveStudentDetails(student: Student): void {

}

saveStudentDetails({
  studentID: 1231
  studentName: 'lala',
});

인터페이스와 메소드 (method)

메소드는 객체내에서 선언된 함수

interface Student {
  studentID: number;
  studentName: string;
  age?: number;
  // 두 가지 방법
  addComment: (comment: string): string;
  addComment: (comment: string) => string;
}

인터페이스에서 사용할 수 있는 Read Only 속성
-> 읽기 전용 프로퍼티로 객체 생성시 할당된 프로퍼티의 값을 바꿀 수 없음!

interface Student {
  readonly studentID: number;
  studentName: string;
  age?: number;
  addComment: (comment: string) => string;
}

function saveStudentDetails(student: Student): void {
  student.studentId = 1122; // read-only error
}

ts를 js로 컴파일할 때 인터페이스를 코드에서 지워버림. 즉 인터페스는 작성 중인 코드에 대한 더 많은 정보를 타입스크립트에게 제공하기 위해 존재하는 것.

타입스크립트에게 더 많은 정보를 제공할수록 우리에게 더 많은 정보를 제공함!!

열거형 (Enum) 과 리터럴 타입

만약 gender 프로퍼티에 들어갈 수 있는 값을 male이나 female 두 가지로 제한하고 싶다면 두 가지 방법이 있음!

열거형 Enum
Enum 이란, 연관된 아이템들을 함께 묶어서 표현할 수 있는 수단

// app.ts
enum GenderType {
  Male,
  Female
}

interface Student {
  studentID: number;
  studentName: string;
  age?: number;
  gender: GenderType;
  subject: string;
  courseCompleted: boolean;
  addComment?: () => string;
}

function getStudentDetails(studentID: number): Student {
  return {
    studentID: 123456,
    studentName: 'Mark Lee',
    gender: GenderType.Male, // <-
    subject: 'Node JS',
    courseCompleted: true,
  }
}

컴파일된 자바스크립트 파일을 보면 컴파일 시 코드가 사라지는 interface와 달리 enum은 코드가 구현돼 파일에 나타남. 이것이 런타임에 나타나는 실제 객체라는 것을 보여줌
Male은 숫자 0, Female은 1이 나타남. 타입스크립트가 enum 속에에 선언된 값의 순서에 따라 0부터 시작해서 순차적으로 증가하는 숫자를 할당한 것.

// app.js
var GenderType;
(function (GenderType) {
    GenderType[GenderType["Male"] = 0] = "Male";
    GenderType[GenderType["Female"] = 1] = "Female";
})(GenderType || (GenderType = {}));

=> 숫자 열거형 Numeric Enum

하지만 enum의 값이 숫자가 아닌 문자열 값을 갖기 원하면?
타입스크립트에선 문자열 열거형 String Enum을 허용함. 그냥 enum의 아이들에게 각각의 값을 할당해 주면 됨

// app.ts
enum GenderType {
  Male = 'male',
  Female = 'male',
  genderNeutral = 'genderNeutral',
}

// 컴파일된 app.js
var GenderType;
(function (GenderType) {
    GenderType["Male"] = "male";
    GenderType["Female"] = "male";
    GenderType["genderNeutral"] = "genderNeutral";
})(GenderType || (GenderType = {}));

숫자형 enum 처럼 자동 증가하는 기능은 없지만 코드를 실행할 때 조금 더 읽기 쉬운 값을줌.

enum말고 다른 방식으로 gender 프로퍼티 값을 제한해보기!

리터럴 타입

그냥 | 로 string 타입을 구분시키기! enum을 사용했을 때와 똑같은 결과를 얻는 코드지만 훨씬 더 읽기 쉽고 간결해 보임

interface Student {
  studentID: number;
  studentName: string;
  age?: number;
  gender: 'male' | 'female' | 'genderNeutral';
  subject: string;
  courseCompleted: boolean;
  addComment?: () => string;
}

function getStudentDetails(studentID: number): Student {
  return {
    studentID: 123456,
    studentName: 'Mark Lee',
    gender: 'male', // <- ts가 autocomlete option을 줌
    subject: 'Node JS',
    courseCompleted: true,
  }
}

유니언 타입, 타입가드, 타입 별칭

Any

어떠한 타입이든 할당 가능!

let someValue: any = 4;
someValue = 'hello'; // 에러없음

타입에 관한 더 많은 정보를 명시할수록 좋음!!!! 효과적인 코드의 유지 보수를 위해 any 타입은 최대한 피하기

타입 명시가 어려운 경우. 예를 들어 서드파티 라이브러리에서 동적 컨텐를 가져와 사용 시 등에만 제한적으로 사용하기

Union Type

let price: number | string = 500;
price = 'free'; // 에러x
price = true; // 컴파일에러

Type Aliases

코드를 짤 때 숫자나 문자열 등 같은 조합이 계속 반복될 경우 어떻게 코드를 향상시킬 수 있을까?

let totalCost: number;
let orderID: number | string;

const calculateTotalCost = (price: number | string, qty: number): void => {
};

const findOrderID = (
  customer: { costomerId: number | string, name: string },
  productId: number | string
): number | string => {
  return orderID;
}

같은 코드를 반복하는 것보다는 코드를 타입으로 지정하고 재활용!

type StrOrNum = number | string;
let totalCost: number;
let orderID: StrOrNum;

const calculateTotalCost = (price: StrOrNum, qty: number): void => {
};

const findOrderID = (
  customer: { costomerId: StrOrNum, name: string },
  productId: StrOrNum
): StrOrNum => {
  return orderID;
}

Type Guards

type StringOrNum = number | string;
let itemPrice: number;

const setItemPrice = (price: StringOrNum): void => {
  itemPrice = price; // price가 string 일 수도 있어서 에러
};

setItemPrice(50);

자바스크립트 내장 기능인 typeof Operator와 조건문 사용

type StringOrNum = number | string;
let itemPrice: number;

const setItemPrice = (price: StringOrNum): void => {
  if(typeof price === 'string') {
	itemPrice = 0;  
  } else {
  	itemPrice = price;
  }
};

setItemPrice(50);

코드 검증을 수행하는 것을 타입가드라고 부름.

다른 방식도 있음. 구글에 typescript type guards 검색
https://www.typescriptlang.org/docs/handbook/advanced-types.html

함수의 타이핑, 선택적 매개 변수와 기본 매개변수

함수의 타입 명시

  • 함수의 반환(Return) 타입
  • 함수의 매개변수 (Parameter)

void 타입 - 아무것도 반환하지 않는 함수의 반환 값으로만 사용될 수 있는 타입

function sendGreeting(message: string, userName: string): void {
  console.log(`${message}, ${userName}`);
}

sendGreeting('Hello', 'Mark');

선택적 매개변수 (Optional Parameter)

타입스크립트는 함수에 정의된 모든 매개변수가 함수에 필요하다고 가정하기 때문에 함수 호출 시 타입스크립트 컴파일러는 함수에 정의된 parameter와 argument를 비교함. 만약 하나의 argument만 전달하고 싶다면?

function sendGreeting(message: string, userName?: string): void {
  console.log(`${message}, ${userName}`);
}
sendGreeting('Hello'); // Hello, undefined

만약 전달되는 매개변수가 여러 개고 몇 가지만 선택적 매개변수인 경우는? 선택적 매개변수들은 반드시 필수 매개변수 뒤에 위치해야 함!

function func(p1: string, p2?: number, p3?: string, p4?: string): void {}

기본 매개변수 (Default Parameter)

function sendGreeting(message: string, userName: string = 'there'): void {
  console.log(`${message}, ${userName}`);
}
sendGreeting('Hello'); // Hello, there

타입추론으로 userName param의 string type을 지워도 됨.

function sendGreeting(message = 'Hello', userName = 'there'): void {
  console.log(`${message}, ${userName}`);
}
sendGreeting(); // Hello, there
sendGreeting('Good morning'); // Good morning, there
sendGreeting('Good evening', 'Jenny'); // Good evening, Jenny

화살표 함수 (Arrow Function)

const sendGreeting = (message = 'Hello', userName = 'there'): void => console.log(`${message}, ${userName}`);

sendGreeting(); // Hello, there

OOP 객체지향 프로그래밍이란? 클래스와 오브젝트 관계 파헤치기

온라인쇼핑 어플리케이션을 구축하기 위해선 주문정보, 고객에대한데이터, 장바구니물건담기, 주문결제처리, 고객정보업데이트 등 수많은 데이터를 담을 변수와 함수가 필요함
이렇게 많은 코드들이 여기저기 흩어져잇으면 가독성이 떨어지고 유지보수가 힘든 스파게티 코드가 됨!

=> 객체지향 프로그래밍은 연관된 변수와 함수들을 한 덩어리로 묶어서 구조화하여 표현하는 프로그래밍 스타일

무엇을 하는 어플리케이션인지 생각해 보기

밑줄 친 단어들은 객체지향 프로그래밍에서 객체가 될 수 있는 아이들

어플리케이션을 실제 세상에 존재하는 객체와 같은 단위로 쪼개고 이러한 객체들(연관된 변수와 함수들의 집합)이 서로 상호 작용함으로써 시스템이 동작!

Class

클래스와 객체는 서로 뗼 수 없는 관계

  • 클라스틑 객체의 뼈대, 설계도, 생산틀
  • 객체들은 클래스를 통해서 만들어질 수 있음

class 속에서 정의된 함수들은 클래스 내 정의된 볏누들에게 this 키워드로 접근 가능하므로 상대적으로 적은 매개변수를 가지는 장점이 있음. 매개변수가 적을수록 함수를 쉽게 사용할 수 있고 쉬운 유지보수가 가능함!

"the best functions are those with no parameters" - Clean Code, Uncle Bob

클래스 내에 정의된 변수 프로퍼티(Property), 클래스 내에 정의된 함수를 메소드(Method)라고 부름

Object

OOP에서 클라스는 객체를 만들어내기 위한 설계도, 생산틀이라고 표현됨.

객체가 클래스를 통해 만들어지고, 그 후에 객체의 프로퍼티 초깃값이 따로 할당됨 -> constructor

class Employee {
  fullName: string;
  age: number;
  jotTitle: string;
  hourlyRate: number;
  workingHoursPerWeek: number;

  printEmployeeDetails = (): void => {
    console.log(`${this.fullName}의 직업은 ${this.jotTitle} 이고 
    일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러이다.`);    
  }
}

let employee1 = new Employee();
employee1.fullName = 'a';
employee1.age = 20;
employee1.jotTitle = '주니어 웹개발자';
employee1.hourlyRate = 40;
employee1.workingHoursPerWeek = 35;
employee1.printEmployeeDetails();
// a의 직업은 주니어 웹개발자 이고 일주일의 수입은 1400 달러이다.  

생성자 (Constructor), 접근 제한자 (Access Modifiers), Getter 와 Setter

모든 Class는 Constructor를 가짐. 클래스의 인스턴스. 클래스로부터 객체를 생성할 때, 호출되며 객체의 초기화를 담당함.

class Employee {
  fullName: string; 
  age: number; 
  jotTitle: string; 
  hourlyRate: number; 
  workingHoursPerWeek: number; 
    
  constructor(
    fullName: string, 
    age: number, 
    jotTitle: string,
    hourlyRate: number, 
    workingHoursPerWeek: number
  ) {
    this.fullName = fullName; 
    this.age = age; 
    this.jotTitle = jotTitle; 
    this.hourlyRate = hourlyRate; 
    this.workingHoursPerWeek = workingHoursPerWeek; 
  }

  printEmployeeDetails = (): void => {
    console.log(`${this.fullName}의 직업은 ${this.jotTitle} 이고 
    일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러이다.`);   
  }
}
constructor(fullName) {
  this.fullName = fullName 
}
// (property) Employee.fullName: string = (parameter) fullName: string

클래스를 통해서 객체를 생성할 때, Consturcor(생성자)에 정의된 Parameter(매개변수들)의 값이, Arguments로 전달되어야 함

let employee1 = new Employee('b', 20, '주니어 웹개발자', 40, 35);
employee1.printEmployeeDetails();
// b의 직업은 주니어 웹개발자 이고 일주일의 수입은 1400 달러이다.

전달되는 매개변수를 강제하고 있음. 반드시 할당하지 않아도 되는 값들로 만들려면 선택적 매개변수로! 선택적 매개변수들은 반드시 필수 매개변수 뒤에 위치

class Employee {
  fullName: string; 
  age: number; 
  jotTitle: string; 
  hourlyRate: number; 
  workingHoursPerWeek: number; 
    
  constructor(
    fullName: string, 
    age: number, 
    jotTitle?: string, // optional parameter
    hourlyRate?: number, 
    workingHoursPerWeek?: number
  ) {
    this.fullName = fullName; 
    this.age = age; 
    this.jotTitle = jotTitle; 
    this.hourlyRate = hourlyRate; 
    this.workingHoursPerWeek = workingHoursPerWeek; 
  }
  ...
}

프로그램을 만들 때 외부로부터 데이터를 보호하기 위해서 이런 식으로 객체 프로퍼티의 접근해서 값을 바꾸는 것을 피해야 함

let employee1 = new Employee('a', 20, '주니어 웹개발자', 40, 35);
employee1.fullName = 'yo'

객체지향 프로그래밍엔 Access Modifiers(접근제한자) 기능이 존재하는데, 클래스 속 멤버 변수 (프로퍼티)와 메소드에 적용될 수 있는 키워드로 클라스 외부로부터의 접근을 통제함! 버그를 줄이고 코드의 안전성을 향상시킴

타입스크립트엔 크게 세 가지 Access Modifiers가 있음

  • Public: 클래스의 외부에서 접근 가능함
  • Private: 클래스 내에서만 접근 가능. 클래스의 외부에서 접근 불가능 (비공개 멤버)
  • Protected: 클래스 내부, 그리고 상속받은 자식 클래스에서 접근 가능

TS에서 기본적으로 클래스 내의 각 멤버들은 public. Public 멤버를 노출시키기 위해 굳이 Public 키워드를 명시할 필요 없음

constructor를 통해 초기화된 객체의 fullName 프로퍼티는 클래스 내의 private 멤버로 더이상 직접적으로 수정하거나 읽을 수 없음.

class Employee {
  private fullName: string; 
  age: number; 
  jotTitle: string; 
  hourlyRate: number; 
  workingHoursPerWeek: number; 
    
  constructor(
    fullName: string, 
    age: number, 
    jotTitle: string, 
    hourlyRate: number, 
    workingHoursPerWeek: number
  ) {
    this.fullName = fullName; 
    this.age = age; 
    this.jotTitle = jotTitle; 
    this.hourlyRate = hourlyRate; 
    this.workingHoursPerWeek = workingHoursPerWeek; 
  }

  printEmployeeDetails = (): void => {
    console.log(`${this.fullName}의 직업은 ${this.jotTitle} 이고 
    일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러이다.`);    
  }
}

let employee1 = new Employee('a', 20, '주니어 웹개발자', 40, 35);
// employee1.fullName = 'ming' // Property 'fullName' is private and only accessible within class 'Employee'.
// console.log(emplyee1.fullName); // 빨간줄 에러
employee1.printEmployeeDetails();

비공개로 설정된 객체의 멤버변수에 접근하여 값을 읽거나 쓰기 위해 게터와 세터라는 컨셉을 제공함

Getter & Setter

클래스 내에서 Get 과 Set 키워드를 사용하여 Getter 와 Setter 를 선언할 수 있음.

비공개 프로퍼티 이름 앞에 _를 긋는 것은 많은 언어에서 convention으로 사용됨. 즉, 프라이빗 멤버임을 나타내기 위한 암묵적으로 약속된 방법

class Employee {
  private _fullName: string; 
  age: number; 
  jotTitle: string; 
  hourlyRate: number; 
  workingHoursPerWeek: number; 
    
  constructor(
    fullName: string, 
    age: number, 
    jotTitle: string, 
    hourlyRate: number, 
    workingHoursPerWeek: number
  ) {
    this._fullName = fullName; 
    this.age = age; 
    this.jotTitle = jotTitle; 
    this.hourlyRate = hourlyRate; 
    this.workingHoursPerWeek = workingHoursPerWeek; 
  }
  
  get fullName() {
    // getter가 불러질 때 실행할 코드
    return this._fullName;
  }
  
  set fullName(value: string) {
    this._fullName = value;
  }
  
  printEmployeeDetails = (): void => {
    console.log(`${this.fullName}의 직업은 ${this.jotTitle} 이고 
    일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러이다.`);    
  }
}

let employee1 = new Employee('a', 20, '주니어 웹개발자', 40, 35);
console.log(employee1.fullName); // fullName getter를 부르는 코드
employee1.fullName = 'ming';
employee1.printEmployeeDetails();
// a
// ming의 직업은 주니어 웹개발자 이고 일주일의 수입은 1400 달러이다.

Constructor 와 Access Modifiers

여전히 길고 반복되는 코드를 ts에서 간단하게 나타낼 수 있는 방법이 존재! -> Constructor 의 매개변수에 Access Modifiers를 직접 적용

이렇게 constructor 매개변수 앞에 access modifiers를 붙여주면 access modifiers가 사용된 constructor parameter는 암묵적으로 class의 field. 멤버변수로 선언됨.

객체가 생성될 때, constructor의 parameter로 전달된 값은 tsc에 의해서 객체의 프로퍼티 값으로 자동으로 그 값이 초기화되고 할당됨.

class Employee { 
  constructor(
    private _fullName: string, 
    private _age: number, 
    private _jotTitle: string, 
    private _hourlyRate: number, 
    public workingHoursPerWeek: number
  ) {

  }
  
  get fullName() {
    // getter가 불러질 때 실행할 코드
    return this._fullName;
  }
  
  set fullName(value: string) {
    this._fullName = value;
  }
  
  printEmployeeDetails = (): void => {
    console.log(`${this.fullName}의 직업은 ${this.jotTitle} 이고 
    일주일의 수입은 ${this.hourlyRate * this.workingHoursPerWeek} 달러이다.`);    
  }
}

let employee1 = new Employee('a', 20, '주니어 웹개발자', 40, 35);
console.log(employee1.fullName); // fullName getter를 부르는 코드
employee1.fullName = 'ming';
employee1.printEmployeeDetails();

드림코딩

6 제네릭

6.1 제네릭 소개

프로그래밍의 꽃 객체지향의 꽃! 재사용성이 굉장히 높은 아이. 이전에 구현했던 스택은 제일 나중에 들어온 것이 처음으로 나가는 자료구조인데, 큰 결함은 문자열만 넣고 뺄 수 있는 것임.

제네릭을 이용하면 우리의 스택을 숫자, 오브젝트 등을 넣고 뺄 수 있고 조금 더 활용성을 높이고 재사용성을 높일 수 있음. 유연하고 타입도 보장 가능

6.2 함수를 제네릭 하게

ts-node app.ts
자바스크립트 querySelector처럼 요소가 리턴될 수도 있고 null이 리턴될 수도 있음. 그래서 아이템이 유효한지 유효하지 않은지 확인하는 함수를 만들 때, 인자가 number, string, object 등을 받아서 체크하고 싶다면? 보장되지 않는 any는 쓰지 않는 것이 좋음. 이럴 때 어떤 타입이든지 받을 수 있는 제네릭을 이용!!!

제네릭은 이것을 쓸 때 타입이 결정되기 때문에 타입을 더 보장받을 수 있음. 제네릭이란 말은 통상적인 일반적인 다 표용하는 느낌

function checkNotNull<T>(arg: T | null): T {
  if (arg == null) {
    throw new Error('not valid number!');
  }
  return arg;
}
// const number: 123 = function checkNotNull<123>(arg: 123 | null): 123
// 숫자가 전달된 이 시점에 checkNotNull은 숫자를 리턴한다고 결정됨
const number = checkNotNull(123);

// const boal: true
const boal = checkNotNull(true);

// const boal: boolean = function checkNotNull<true>(arg: true | null): true
const boal: boolean = checkNotNull(true);

제네릭을 이용하면 사용하는 사람이 어떤 타입인지 결정할 수 있고, 컴파일 시간 때 우리가 코딩을 할 때 타입을 보장받을 수 있음!

그리고 타입은 <GENERIC> 이렇게 길게 쓰지 않고 보통은 <T> 대문자 하나만 씀. 저 함수를 보자마자 제네릭 함수고, 이 함수는 T 또는 null이라는 타입을 받고 T를 다시 리턴하는구나 이해!

6.3 클래스를 제네릭 하게

제네릭은 길게 쓰지 않고 대문자 하나만! 제네릭을 사용하면 활용성이 높은 클래스와 함수를 만들 수 있음. item의 I와 value의 V도 많이 씀. 의미는 있지만 짧은 CHAR로 타입을 정의하기

// either: a or b
interface Either<L, R> {
  left: () => L;
  right: () => R;
}

class SimpleEither<L, R> implements Either<L, R> {
  constructor(private leftValue: L, private rightValue: R) {}
  left(): L {
    return this.leftValue;
  }
  right(): R {
    return this.rightValue;
  }
}
const either: Either<number, number> = new SimpleEither(4, 5);
either.left(); // 4
either.right(); // 5
const best: SimpleEither<
  {
    name: string;
  },
  string
> = new SimpleEither({ name: 'g' }, 'hello');

6.4 제네릭 조건

세부 클래스의 정보를 잃어버린 경우. 정말정말정말 자신이 있따면 가능해도 as를 쓰는 건 좋지 않음. 세부적인 타입을 인자로 받아서 정말 추상적인 타입으로 다시 리턴하는 함수는 똥이다 💩

function payBad(employee: Employee): Employee {
  employee.pay();
  return employee;
}

타입을 광범위하게 만드는 행위는 정말 위험!

제네릭은 너무 일반적인 타입으로 어떤 타입이든 들어올 수 있기 때문에 우리가 코딩하는 이 시점에는 employee에 play가 있는지 없는지 숫자인지 스트링인지 이런 타입에 대한 정보가 없음. 이때 constraints 조건을 달 수 있음

interface Employee {
  pay(): void;
}
// 제네릭이긴 제네릭인데 이 타입은 employee를 확장한 애들만 돼!
function pay<T extends Employee>(employee: T): T {
  imployee.pay();
  return employee;
}

이처럼 제네릭도 이런 조건들을 걸어둠으로서 조금 더 제한적인 범위 내에서 일반화된 제네릭을 이용할 수 있음.

6.5 제네릭 조건 예제

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

타입스크립트에서 any를 사용하는 건 가급적 지양하기 :)

우리가 사이드 프로젝트를 할 때, 오픈소스 프로젝트를 참가할 때 회사에서 일을 할 때 내가 지금 담당하고 있는 이 기능, 컴포넌트, 서비스 로직, 이 클래스가 재사용될 필요성이 있는지 한 번 질문을 해보고 재사용될 가능성이 있고 중요하다면 제네릭을 이용해서 구현하기!

8 코딩 실력 향상 하기

8.1 API 읽어 보기

자바스크립트 언어나 라이브러리 자체에서 제공하는 api를 이용할 때 공식 문서를 일일이 찾아보는 것보다 빠르게 정의된 곳으로 가서 어떤 스펙으로 작성돼 있는지 빠르게 확인하기

// 7-API/7-1-api.ts
Array; // ctrl or command를 누른 상태에서 마우스를 올려 선택

lib.es5.d.ts 처럼 d라고 하는 것은 타입이 정의돼 있는 Type definition 또는 Type declaration 이라고 함. 바로 타입이 정의된 것! 이건 타입스크립트 팀에서 우리가 자바스크립트 코드를 작성하면서 라이브러리를 써야할 때 JS 언어 자체에서 제공하는 Array, DOM 이런 타입이 없는 자바스크립트를 조금 더 명확하게 타입이 어떤 것인지 확인할 수 있도록 추가적으로 타입을 정의해 놓은 파일

스스로 한 번 처음부터 끝까지 읽어 보기..^^

정의가 나오지 않는다면 타입스크립트는 오픈소스 프로젝트라 다 깃허브에서 확인할 수 있음.
https://github.com/microsoft/TypeScript/blob/master/lib/lib.es5.d.ts

이렇게 동일한 이름의 두 가지 다른 타입의 인자를 받는 함수를 오버로딩 함수 오버로딩 이라고 함. 전달되는 인자 타입에 따라서 첫 번째 concat이 호출될 수가 있고 두 번째 것이 호출될 수 있음

// lib.es5.d.ts
interface Array<T> { 
...
    concat(...items: ConcatArray<T>[]): T[];
    /**
     * Combines two or more arrays.
     * This method returns a new array without modifying any existing arrays.
     * @param items Additional arrays and/or items to add to the end of the array.
     */
    concat(...items: (T | ConcatArray<T>)[]): T[];
    /**
     * Adds all the elements of an array into a string, separated by the specified separator string.
     * @param separator A string used to separate one element of the array from the next in the resulting string. If omitted, the array elements are separated with a comma.
     */
...

optional라고 되어 있어서 구분자를 전달해도 되고, 전달하지 않아도 됨. 그리고 따로 전달하지 않으면 comma로 분리해 줄 거다!

    join(separator?: string): string;
    /**
     * Reverses the elements in an array in place.
     * This method mutates the array and returns a reference to the same array.
     */

9 에러 처리 하기

9.1 에러 처리 소개

Error Exception Handling
어플리케이션을 사용하다 보면 예상하지 못한 오류가 발생하거나 메모리에 문제가 있어서 갑자기 중지되는 경우가 있음. Exception은 예상하지 못한 Error를 말함

사용자가 사용할 때 터지게 놔두지 않고 잘 처리하고 Handling 하는 것이 중요! 그러면 안전성도 높아지고 유지 보수성도 높일 수 있음

예상하지 못한 이슈를 어플리케이션 내부에서 잘 해결하려고 노력을 하다가 정 안 되면 사용자에게 '미안한데 문제가 발생했어'라고 알려주는 역할을 해야 함. 우리가 앱 내부에서 코딩을 할 때
예상할 수 있는 것을 Error State 라고 함.

중요한 포인트!

  • 우리가 예상을 할 수 있고 예상이 되어지는 Error State 인지
  • 정말 전혀 예상하지 못한 Error Exception 인지
    두 가지를 구분해서 사용할 필요가 잇음

9.2 Error에 대해

exception은 다른 프로그래밍 언어에서는 클래스 오브젝트가 있고, 자바스크립트나 타입스크립트에서는 Error라는 클래스가 있음.

타입스크립트 컴파일러가 똑똑해서 모든 케이스가 되고 여기 올 수 있는 direction은 never 상태 밖에 없기 때문에 Error 발생

function move(direction: 'up' | 'down' | 'left' | 'right' | 'he') {
  switch (direction) {
    case 'up':
      position.y += 1;
      break;
    case 'down':
      position.y -= 1;
      break;
    case 'left':
      position.x -= 1;
      break;
    case 'right':
      position.x += 1;
      break;
    default:
      const invalid: never = direction; // trick
      throw new Error(`unknown direction: ${invalid}`);
  }
}

9.3 에러 처리의 기본 (try, catch, finally)

Error(Exception) Handling: try 에러가 발생할 수 있는 부분을 시도 -> catch 에러가 발생한다면 잡고 -> finally 에러가 발생하든 발생하지 않든 마무리
try와 catch로 에러를 잡고 조금 더 의미있는 에러메세지를 사용자에게 보여준다던지의 노력. 에러메세지는 친절하게 error에 관련된 정보의 디테일한 내용들을 담아서!

예상하지 못한 에러가 발생할 수 있는 함수를 쓸 때는 최대한 에러가 발생할 수 있는 정확한 부분에서만 try를 감싸주면 됨.

function run() {
  const fileName = 'not exist!💩';
  try {
    console.log(readFile(fileName));
  } catch (error) {
    console.log(`catched!!`);
    return;
  }
  closeFile(fileName);
  console.log(`finally! closed!`);
}

catch 안에서 뭔가를 처리할 때 다른 에러가 발생하거나 리턴이 되거나 이런 경우엔 closeFile-파일을 닫을 수 없기 때문에 가능하면 try 하는 것과 연관되어있는 마지막에 마무리 해야되는 것들은 finally 안에서!

function run() {
  const fileName = 'not exist!💩';

  try {
    console.log(readFile(fileName));
  } catch (error) {
    console.log(`catched!!`);
    return;
  } finally {
    closeFile(fileName);
    console.log(`finally! closed!`);
  }
}

finally 안에 있는 코드는 에러가 발생하고 catch가 되어도 항상 실행된다는 걸 보장하기 때문에 finally에서 하는 게 좋음. try 안에서는 코드를 추가해 블럭을 지저분하게 쓴느 것보다는 정말 에러가 발생하는 그 부분만 try로 감싸서 catch를 하고 finally 하는 것이 더 좋음

9.4 우아하게 에러를 처리하는 방법

다양한 곳에서 사용하다가 에러가 발생한다면 어디에서 try, catch를 하는 것이 좋을까? 에러가 발생했을 때 내가 정확하게 우아하게 처리할 수 있는 것이 아니라면 catch 하지 않는 것이 더 나음. 그래서 어정쩡하게 잡기보다는 이것을 처리할 수 있는 곳에서 try 하는 것이 더 좋으!

예상하지 못한 에러가 발생하는 그런 것이 있다면 내가 이것을 try catch Handling 할 대 여기서 처리하는 것이 과연 의미가 있을까라고 생각해 보고 가능한 가장 우아하게 처리할 수 있는 곳에서 catch

9.5 막강한 Error State 👍

만약 tryConnect 여기에서 발생할 수 있는 에러의 종류가 많다면 예를 들어 타임아웃, 오프라인 등 에러가 난 종류에 맞게 세부적인 클래스 던질 때..

근데 아쉬운 문제점은 catch에 전달되는 이 에러는 any type

class OfflineError extends Error {}
class App {
  constructor() {}
  run() {
    try {
      //
    } catch (error) {
      if(error instanceof OfflineError) {
        // instanceof를 사용할 수 없음 왜냐면 catch로 에러를 받는 순간 
        // any type이 되기 때문에 타입에 대한 정보가 사라짐
      }
    }
  }
}

참고: TypeScript에서 구현된 catch()에는 어떠한 타입정보도 전달되지 않아서 instanceOf를 사용할 수 없어요 😭

그래서 Exception 에러는 가급적 정말 예상하지 못한 곳에서 에러가 발생할 때 쓰는 게 더 좋고, 조금 더 세부적인 에러를 결정하고 싶을 때는 ErrorState를 사용!

네트워크 에러가 발생할 수 있는 것은 사실 코드를 작성할 때 예상할 수 있는 state. 예상하지 못하게 throw를 남발하지 않고, tryConnect 한 다음 어떤 상태가 되는지 ResultState를 리턴하게 만들기! reason을 조금 더 세부적으로 작성

type NetworkErrorState = {
  result: 'fail';
  reason: 'offline' | 'down' | 'timeout';
};
type SuccessState = {
  result: 'success';
};
type ResultState = SuccessState | NetworkErrorState;
class NetworkClient {
  tryConnect(): ResultState {
    return {
      result: 'success',
    };
  }
}

Unions and Intersection Types

가급적이면 프로그래밍을 할 때 내가 예상할 수 있는 상태 성공적인 상태와 실패적인 상태 이런 것들을 타입으로 정의하는 것이 안정적이고 예상 가능하게 프로그래밍 할 수 있음

10 타입스크립트의 핵심 🚀

10.2 Type Alias 와 Interface 뭘 써야 할까? (기술 측면)

Type(or Type alias) vs Interface!
초창기에는 타입과 인터페이스는 많이 달랐고 인터페이스가 할 수 있는 것들이 더 많았음. 타입과 인터페이스는 엄연히 다르고 성격도 특징도 다름!

둘의 차이는 어떤 것인지 조금 더 구현 사항에 초점을 두고 알아보깅

type PositionType = {
  x: number;
  y: number;
};
interface PositionInterface {
  x: number;
  y: number;
}

// object ★
// 둘 다 가능! 오브젝트 형태로 만들 수 있음
const obj1: PositionType = {
  x: 1,
  y: 1,
};
const obj2: PositionInterface = {
  x: 1,
  y: 1,
  z: 1,
};

// class ★
// 둘 다 클래스에서 구현이 가능함
class Pos1 implements PositionType {
  x: number;
  y: number;
}
class Pos2 implements PositionInterface {
  x: number;
  y: number;
}

// Extends
// 상속을 통해서 확장
interface ZPositionInterface extends PositionInterface {
  z: number;
}
// intersection을 이용해 묶은 타입
type ZPositionType = PositionType & { z: number };

타입스크립트 초창기에는 이런 타입을 이용해 확장이 불가능하고 할 수 없는 것들이 많았음.

오직 interface들만 merge가 될 수 있음. 타입은 X

interface PositionInterface {
  x: number;
  y: number;
}
interface PositionInterface {
  z: number;
}
const obj: PositionInterface = {
  x: 1,
  y: 1,
  z: 1.
}

대신 타입만 가능한 것이 있음. 유틸리티나 맵 타입이나 인덱스 타입도 이용 가능함. computed properties를 사용 가능. 유니온 타입은 인터페이스에서 절대 구현할 수 없음

type Person = {
  name: string;
  age: number;
};
type Name = Person['name']; // string

type NumberType = number;
type Direction = 'left' | 'right';

갈수록 많이 비슷해지고 있고 현재 구현 사항은 이런 차이점이 있다..!

10.3 Type Alias 와 Interface 뭘 써야 할까? (개념 측면)

Interface
  • 어떤 것들의 규격사항. 의사소통을 정해진 인터페이스를 통해 상호작용. => 약속. 계약서.
  • 어떤 특정한 규격(커피메이커)을 정의하는 것이라면 규격을 통해서 어떤 것이 구현된다면 인터페이스를 쓰는 것이 더 정확!
Type
  • 어떠한 데이터를 담을 수 있을지 이 데이터의 모습. 타입을 결정하는 것.
  • ex => type Position = { x: number; y: number }
  • 인터페이스를 보게 되면 Position을 구현하는 어떤 클래스가 있는가라는 생각. 어떠한 것을 구현할 목적으로 만드는 것이 아니라 데이터를 담을 목적

10.4 Utility Type이란?

✮ ―transform→ ✩
타입스크립트는 타입을 변환하는 것이 가능!

Index Type

type도 인덱스를 기반으로 해서 타입을 결정할 수 있음. 다른 타입에 있는 키에 접근해서 그 키의 value 타입을 그대로 다시 선언할 수 있음

{
  type Animal = {
    name: string;
    age: number;
    gender: 'male' | 'female';
  };

  type Name = Animal['name']; // Name type = string
  // const text: Name = 1; // error
  const text: Name = 'hello';

  type Gender = Person['gender']; // 'male' | 'female'

  // 총 세 가지 문자열 유니온 타입이 할당됨
  type Keys = keyof Animal; // 'name' | 'age' | 'gender'
  // const key: Keys = 'na'; // error 
  const key: Keys = 'gender';

  type Person = {
    name: string;
    gender: Animal['gender'];
  };
  const person: Person = {
    name: 'ellie',
    gender: 'male', // 'male' | 'female'
  };
}

10.6 Mapped Type

기존에 있는 타입들을 이용하면서 조금 다른 형태로 변환할 수 있는 것! 공통된 타입을 Optional, readonly 타입으로 만들고 싶은데 변경이 될 때마다 이것을 다 복사해서 수정하지 않고 간편하고 재사용성을 높일 수 있게 하는 방법이 맵 타입

기존의 타입을 다른 형태로 어떻게 변환?!
오브젝트에 있는 모든 키들을 하나하나씩 도는 연산자 for in을 썼을 때와 동일하게 []를 쓰면 돌 수 있음

// Optional 타입은 어떤 종류의 다른 타입도 받아와 맵으로 재사용
type Optional<T> = {
  // P라는 것은 T 타입의 모든 키들 중에 하나의 P라는 키(이 프로퍼티)는 
  // 동일하게 T 오브젝트 안에 있는 키를 이용해 value를 정의
  [P in keyof T]?: T[P] // for...in
}
type Animal = {
  name: string;
  age: number;
};
const animal: Optional<Animal> = {
  name: 'dog',
};

const video: ReadOnly<Video> = {
  title: 'hi',
  author: 'ellie',
};

type Nullable<T> = { [P in keyof T]: T[P] | null };

10.7 Conditional Type

타입에도 조건을 줄 수 있음! 어떤 타입이 이런 타입이라면 이 타입을 써야지라고 조건적으로 결정할 수 있는 타입

// 기존에 주어진 타입이 문자열을 상속한다면 Boolean 타입으로 아니면 넘버 타입으로 결정
type Check<T> = T extends string ? boolean : number;
type Type = Check<string>; // boolean

10.8 ReadOnly 😜

인덱스 타입, 맵 타입, 컨디션 타입은 우리가 기존에 있는 타입을 보장하고 유지하고 재사용하면서 조금 다른 종류의 타입을 만들고 싶을 때 활용할 수 있음

ReadOnly, Partial, Required 등과 같이 흔하게 쓰이는 타입들은 사실 유틸리티 타입이라고 해서 공통적으로 필요한 타입들을 이미 타입스크립트 개발자들이 만들어 놔서 우리가 작성하지 않아도 됨. 이것들을 잘 사용하기!

type ToDo = {
  title: string;
  description: string;
};

// ctrl+클릭 하면 모든 유틸리티 클래스가 정의되어져 있음.
// type Readonly<T> = { readonly [P in keyof T]: T[P]; }
function display(todo: Readonly<ToDo>) {
  // 항상 불면성을 보장하는 것이 좋음!
  // todo.title = 'jaja';
}

// 마이너스는 Optional이 아니라 그 반대의 절대적인 절대로 있어야 되는 것을 나타냄
type Required<T> = {
  [P in keyor T]-?: T[P];
}

10.9 Partial Type

기존의 타입 중에서 부분적인 것만 허용하고 싶을 때 이용함

type ToDo = {
  title: string;
  description: string;
  label: string;
  priority: 'high' | 'low';
};
// 엉뚱한 키와 value를 전달할 수 없음
function updateTodo(todo: ToDo, fieldsToUpdate: Partial<ToDo>): ToDo {
  return { ...todo, ...fieldsToUpdate };
}

Partial<T>가 T의 모든 프로퍼티를 optional로 만듦

10.10 Pick Type ⛏

기존의 타입에서 원하는 속성과 value들만 뽑아다 조금더 제한적인 타입을 만들고 싶을 때 사용어떠한 정보가 많은 타입이 있고 그 중에 몇 가지만 다루는 타입이 있다면 이용

type Video = {
  id: string;
  title: string;
  url: string;
  data: string;
;

// Video 타입에서 id와 title 정보만 이용한 타입
type VideoMetadata = Pick<Video, 'id' | 'title'>;

항상 정의된 부분에서 어떻게 구현되어 있는지 확인..

// lib.es5.d.ts
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

10.11 Omit Type

Pick과 반대로 원하는 것을 빼버릴 수 있음. 내가 빼고자 하는 것이 더 명확하다면 Omit을 선택하고자 하는 것이 간단하다면 Pick을 이용!

type Video = {
  id: string;
  title: string;
  url: string;
  data: string;
;
type VideoMetadata = Omit<Video, 'url' | 'data'>;

// lib.es5.d.ts
// 다른 어떤 종류의 키도 전달 가능함. T에 있는 키들 중에 K를 제외한 아이들만 Pick
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// U가 T 안에 들어 있다면 그 타입은 절대 쓰지 않고 T가 U 안에 없을 때만 T를 이용함
type Exclude<T, U> = T extends U ? never : T;

10.12 Record, 이건 뭐지?

레코드라는 타입은 맵과 비슷하게 하나와 어떤 하나를 연결하고 싶을 때 하나를 키로 쓰고 나머지를 다른 타입으로 묶고 싶을 때 유용하게 쓸 수 있음

type PageInfo = {
  title: string;
};
type Page = 'home' | 'about' | 'contact';

// Page를 키로 삼고 PageInfo를 value로 삼으면 됨
const nav: Record<Page, PageInfo> = {
  home: { title: 'Home' },
  about: { title: 'About' },
  contact: { title: 'Contact' },
};

10.13 기타 아이들

type Product = 'cat' | 'dog';
type NewProduct = Capitalize<Product>; // 'Cat' | 'Dog'

Lowercase, Uncapitalize, ReturnType(함수의 리턴 타입 결정) 등등 유틸리티 타입이 많음~!

JavaScript 정리

11.2 프로토타입이란?

타입스크립트는 자바스크립트의 superset으로 자바스크립트에 없었던 클래스와 인터페이스, 제네릭 이런 아이들을 이용해 객체지향 프로그래밍을 할 수 있었음. 자바스크립트는 프로토타입을 기반으로 객체지향 프로그래밍이 가능

프로토타입은 상속을 위해 쓰임. 단어의 의미는 무언가 프로토타입을 만들다. 대략적인 그림을 만들다 추상하다 추상적인 무언가를 만든다 느낌. (로봇은 이런 기능을 할 거야) 반복적으로 쓸 수 있도록 속성과 함수들을 정의하는 것!

브라우저 개발툴 콘솔창 확인
자바스크립트의 모든 오브젝트들은 오브젝트라는 프로토를 상속함

const x = {};
const y = {};
console.log(x.toString()); // [object object]
console.log(x.__proto__ === y.__proto__); // true

array 프로토 안에는 object proto도 가지고 있음. arr라는 변수의 오브젝트는 array를 상속하고 이 array라는 프로토는 바로 오브젝틀르 상속함


그래서 어떠한 종류의 오브젝트든 상관없이 toString을 이용할 수 있는 것이 그 이유

Instance member level
constructor function 안에 만들면 만들어지는 오브젝트마다 공통된 함수를 가지고 있음.
-> Prototype member level

프로토타입은? 자바스크립트에서 객체지향 프로그래밍 상속을 하기 위해서 쓰이는 아이로 코드를 재사용할 수 있음.

11.4-5 This

다른 객체지향 프로그래밍 언어에서 this란 바로 클래스 자신을 가리키는 것을 말함. 반대로 자바스크립트에서 this란 지니🧞와 비슷. 누가 부르냐 호출했냐 호출한 문맥에 따라서 달라짐

브라우저 환경에서는 윈도우가 글로벌 객체로 윈도우가 this

변수 func에서 this의 정보를 잃어버림

우리가 선언한 함수는 기본적으로 윈도우 객체에 등록됨. 하지만 const나 let 키워드를 이용해 변수를 선언하면 등록되지 않음. (var 키워드 예외..)

var은 호이스팅 문제와 재정의 등 다양한 문제점으로 가급적 사용하지 않기!!

자바스크립트에서 this란 부르는 문맥에 따라 변경될 수 있으므로 한 곳에서 오브젝트와 연결하고 싶다면 bind 함수를 이용해서 묶어줘야 됨. 또는 클래스 안에서 함수를 선언할 때 arrow function을 이용. 얘는 선언될 당시에 그 당시 문맥 - 스코프의 this context를 유지함.

11.6 모듈이란?

자바스크립트에서 모듈이란 코드를 모듈화해서 작성하는 것을 말함. 즉 한 모듈이라고 하는 것은 한 파일 안에 작성되어 있는 코드

모듈화하지 않으면 모든 코드들은 기본적으로 글로벌 스코프로 측정되어 브라우저 환경 윈도우나 노드 환경이면 글로벌에 다 등록되어 라이브러리에서 동일한 함수 이름 충돌이 발생할 확률이 높음. 규모가 조금이라도 큰 프로젝트라면 모듈화를 이용해 코딩하는 것이 안전

// module.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="module1.js"></script>
    <script src="module2.js"></script>
  </head>
  <body></body>
</html>
// module1.js
function add(a, b) {
  return a + b;
}

// module2.js
console.log(add(1, 2));

모듈하려면 html에서 type을 module로 정의

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script type="module" src="10-3-module1.js"></script>
    <script type="module" src="10-3-module2.js"></script>

default로 export를 하면 이름을 우리가 원하는대로 다시 정의할 수 있음. 한 파일 내에서 두 가지의 default를 쓸 수 없고 default가 아닌 아이들은 중괄호를 이용해서 가져오기. as로 이름 변경 가능. 변수같은 것도 다 export 할 수 있음

// module1.js
export default function add(a, b) {
  return a + b;
}
export function printHello() {
  console.log('hello');
}

// module2.js
import sum, { printHello as print } from './module1.js';
console.log(sum(1, 2));
print();

타입스크립트 컴파일러

12.1 TSConfig 셋업하기! (다수의 파일 실시간 컴파일 설정)

파일이 변경될 때마다 자바스크립트로 변환해주는 tsc 타입스크립트 툴을 이용해 watch 모드로 동작. 프로젝트 안에 여러 타입스크립트 파일을 동시에 실행하고 싶다면 tsc --init를 입력하면 컴파일러 옵션들이 들어있는 tsconfig 파일이 생김. 이제는 tsc -w로 tsconfig가 있는 프로젝트 폴더 안에 있는 모든 타입스크립트를 자바스크립트로 변환 가능

12.2 프로젝트 구조 정리 하기

우리가 따로 설정하지 않으면 항상 작업하고 있는 타입스크립트와 컴파일된 자바스크립트가 섞여 나옴. 어떤 게 내가 작업한 건지 어떤 게 컴파일된 건지 확인하기 힘들고 실수로 자바스크립트 파일 코드를 수정할 확률이 높아짐

보통은 생성된 자바스크립트 파일들을 다른 디렉토리에 넣어둠. 어디에다 컴파일된 파일을 어디에 지정할 건지 tsconfig에서 outDir 옵션에 설정. 기본적으로 항상 현재 디렉토리 안에 생김

// tsconfig.json
"outDir": "./",   

타입스크립트가 있는 프로젝트 안에서 제일 최상위 루트부터 첫 번째로 타입스크립트 파일이 나오는 곳부터 상위폴더가 지정됨

타입스크립트가 있는 최상위부터 반영됨. 보통은 최상위에서 폴더를 나누지 않고 소스 디렉토리 안에 필요한대로 폴더를 나눠서 시작

연결!

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script src="./build/main.js"></script>
    <script src="./build/logging/logging.js"></script>
  </head>
  <body></body>
</html>

다른 사람이 이 소스 디렉토리 외에서 밖에서는 절대 타입스크립트 파일을 생성할 수 없게 만드려면 루트 디렉토리라는 것을 설정! 기본적으로는 최상위 "rootDir": "./",로 설정됨

{
  "compilerOptions": {
    ...
    /* Modules */
    "module": "commonjs",
    "rootDir": "./src",
    ...
}

또 compilerOptions 밑에 어떤 파일들을 추가할 건지 제외할 건지 설정할 수 있음

{
  "compilerOptions": {...
  },
  // "include": ["./src/dev.ts"], // dev.ts만 컴파일
  "exclude": ["./src/dev/ts"],
}

보통은 소스 디렉토리 안에 우리가 만들고자 하는 기능 단위로 디렉터리를 만들고 그 안에 관련된 파일들을 만들어서 작업하는 게 일반적

12.3 컴파일러 옵션들 파헤치기

https://www.typescriptlang.org/tsconfig/

{
  "compilerOptions": {
    // "incremental": true, // 수정된 내용만 컴파일해서 컴파일 시간이 빨라짐
    "target": "ES5", // 어떤 버전으로 컴파일. 무족 낮은 버전 x 필요한! 보통은 es5나 6
    "module": "ES6", // 컴파일될 때 모듈을 어떻게 구현할 건지 결정
    // "allowJs": true, // 자바스크립트와 타입스크립트를 섞어서 만들 건지
    // "checkKJs": true, // 한 프로젝트 안에서 둘 다 섞어서 진행할 거면 두 가지 모두 true
    // "sourceMap": true, // 디버깅할 때 유용
    // "outFile": "./", // 다수의 타스 파일을 딱 하나의 자스 파일로 만들 때
    // "noEmit": true, // 자스 코드로 변환 x, 컴파일 에러가 있는지 없는지만 확인
    ...
  }
}

12.4 디버깅 하는 방법 🐛

브라우저 Sources 탭에서 컴파일된 코드를 일일이 읽어보고 break point를 잡아 디버깅하는 것은 괴롭.. tsconfig에서 sourceMap 옵션을 true로 설정하면 우리가 작성한 타스 코드와 컴파일된 자스 코드를 연결시켜주는 모든 정보들이 담겨져 있는 map이라는 파일을 생성함

브라우저 디버깅 툴에서 이해할 수 있음


Typescript:기초부터 실전형 프로젝트까지 with React + NodeJS

TypeScript란 무엇이며 왜 사용해야 하는가

  • 타입스크립트는 자바스크립트의 슈퍼셋. 결국 자바스크립트 기반 프로그래밍 언어
  • 브라우저나 nodejs 에서 실행할 수 없음. 타스는 프로그래밍 언어이면서 도구 - 코드를 실행하며 타입스크립트 코드를 자바스크립트로 컴파일하는 컴파일러 -> 결과적으로 일반적인 자바스크립트 코드를 얻게됨
  • TS가 결국 일반적인 JS라면 어떻게 TS로 새로운 기능을 추가할 수 있을까?

Using Types

  • number, string, boolean, object, Array, Tuple, Enum, Any
  • number - 자바스크립트와 마찬가지로 number 타입은 하나. 정수형 실수형에 해당하는 특별한 타입은 없음
  • string 타입 - '', "", ``과 같은 세 가지 방법 중 하나로 정의할 수 있음
  • boolean - true or false
  • 컴파일하는 동안에만 유용. 브라우저에는 내장 타입스크립트 지원이 없기 때문에 런타임에서 자바스크립트가 다른 식으로 작동하도록 변경하지 않음
  • TS는 JS보다 훨씬 많은 타입을 알고 있음. 따라서 런타임 검사는 TS로 수행할 수 있는 것만큼 유연하거나 강력하지 않음. 타입스크립트 기능과 검사 기능이 JS 엔진에 내장되어 있지 않기 때문에 개발 도중에만 지원받을 수 있음. => 개발 도중 코드를 컴파일할 때만 실행 가능
  • 타입스크립트 타입은 컴파일 중에 확인되는 반면, 자바스크립트 타입은 런타임 중에 확인됨

Working with Numbers, Strings & Booleans

  • 모든 숫자형은 기본적으로 float 실수형. 5=5.0
  • 타입 추론(type inference) 내장 기능 활용해 특정 변수나 상수에 어떤 타입을 사용했는지 잘 이해할 수 있음. 추론되지 않은 잘못된 타입을 사용하면 에러를 출력
let resultPhrase = 'Result is:';
resultPhrase = 0; // error

Object Types

  • JS에서는 배열에 문자열, 불리언, 객체, 배열, 중첩 배열 등 여러 데이터를 저장할 수 잇음. TS에서는 배열에 무엇이든 지정 가능함
  • any는 유연하지만 모든 장점, 타입, 좋은 기능들을 활용하지 못하게 함. 되도록 쓰지 않기

Working with Tuples

JS엔 없는 타입으로 길이와 타입이 고정된 배열!

role key에 마우스 올리면 TS가 낯선 타입을 추론함

const person = {
  name: '',
  age: 10,
  hobbies: [],
  role: [2, 'author] // role: (string | number)[]
}

우리는 role의 요소 중 첫 번째 요소는 숫자, 두 번째는 문자열이어야 한다는 걸 TS는 알지 못함. 바로 이런 경우에 튜플이 적합함
튜플은 특별한 구조

const person: {
  name: string;
  age: number;
  hobbies: any[];
  role: [number, string]; // 튜플 타입이 됨
} = {
  name: '',
  age: 10,
  hobbies: ['Cooking', 'Dancing'],
  role: [2, 'author'],
}

Working with Enums

자바스크립트의 일반적인 패턴

const ADMIN = 0;
const READ_ONLY = 1;
const AUTHOR = 2;
const person = {
  ...
  role: ADMIN
};
if(person.role === ADMIN) {...}

enum은 작업을 수월하게 해줌

enum Role { ADMIN, READ_ONLY, AUTHOR }; // 0, 1, 2
if(person.role === Role.ADMIN) {

컴파일된 자바스크립트 코드

(function (Role) {
    Role[Role["ADMIN"] = 0] = "ADMIN";
    Role[Role["READ_ONLY"] = 1] = "READ_ONLY";
    Role[Role["AUTHOR"] = 2] = "AUTHOR";
})(Role || (Role = {}));

다른 숫자 입력 가능. 그럼 값을 할당한 다음 식별자 시작 값 증가

enum Role = { AMIN = 5, READ_ONLY, AUTHOR }; // 5, 6, 7

// 필요에 따라 임의의 숫자를 모든 식별자에 고유한 값으로 할당
enum Role { ADMIN = 5, READ_ONLY = 100 };

// 텍스트도 할당할 수 있고 혼합도 가능..
enum Role { ADMIN = 'ADMIN', READ_ONLY = 100 };

The "any" Type

  • 가장 유연한 타입. 모든 값을 저장 가능하지만 타입스크립트가 주는 모든 장점을 상쇄시켜 바닐라 자바스크립트를 쓰는 것과 다를 바 없게됨.
  • 타입스크립트 컴파일러가 작동하지 않게 됨. 따라서 어떤 값이나 종류의 데이터가 저장될지 전혀 알 수 없는 경우 대비하거나 런타임 검사를 수행하는 경우 사용

Union Types

- 서로 다른 두 종류의 값을 사용해야 하는 애플리케이션에서 상수 혹은 변수의 매개변수를 사용해야 한다면 유니언 타입을 사용
function combine(input1: number | strint, input2: number | string) {
  const result = input1 + input2;
  return result;
}

const combinedAges = combine(10, 20);
const combinedNames = combine('Lulu', 'Lala');

근데 유니언 타입을 사용하면 타입에 따라 함수 내에 다른 로직을 적용할 수 잇음. 종종 런타임 검사가 필요한 경우가 있음

function combine(input1: number | string, input2: number | string) {
  let result: number | string;
  if (typeof input1 === 'number' && typeof input2 === 'number') {
    result = input1 + input2;
  } else {
    result = input1.toString() + input2.toString();
  }
  return result;
}

Literal Types

유니언 타입을 리터럴 타입과 결합하여 사용하여, 아무 문자열이 아닌 이 두 문자열만 특정하여 허용

function combine(
  resultConversion: 'as-number' | 'as-text'
) { 

Type Aliases / Custom Types

더 복잡한 타입 정의를 원한느 타입이나 타입 이름으로 인코딩할 수 있음

type Combinable = number | string;
type CombinableDescriptor = 'as-number' | 'as-text';

Type Aliases & Object Types

타입 별칭을 사용하여 타입을 직접 “생성”할 수 있습니다. 유니온 타입을 저장하는 것만 가능한 것이 아닙니다. 복잡할 수 있는 객체 타입에도 별칭을 붙일 수 있습니다.

예:

type User = { name: string; age: number };
const u1: User = { name: 'Max', age: 30 }; // this works!

타입 별칭을 사용하면 불필요한 반복을 피하고 타입을 중심에서 관리할 수 있습니다.

예를 들어, 다음 코드를 아래와 같이 단순화할 수 있습니다.

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;
}

단순화 후:

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;
}

Function Return Types & "void"

함수에는 타입보다 중요한 반환(return) 타입
function add(n1: number, n2: number): number
마지막 매개변수 목록에서 : 다음에 오는 부분

아무것도 반환하지 않는 함수에 void를 명시적으로 지정할 수 있지만 ts는 추론 가능

// function printResult(num: number): void
function printResult(num: number) {
  console.log('Result :', num);
}

드물게 undefineds는 실제 값을 반환하지 않을 때 사용할 수 있음

Functions as Types

함수타입을 명시. 함수 타입은 함수의 매개변수와 반환값에 관련된 함수를 설명. 자바스크립트의 화살표 함수 표기법에 가깝에 만들어짐.

function add(n1: number, n2: number): number {
  return n1 + n2;
}

// 매개변수 이름을 유형에 맞춰 일치시킬 필요는 없음
let combineValues: (a: number, b: number) => number;

combineValues = add;

Function Types & Callbacks

콜백과 함수 타입은 거의 같은 방식으로 작동함.

function addAndHandle(n1: number, n2: number, cb: (num: number) => void) {
  const result = n1 + n2;
  cb(result);
}

addAndHandle(10, 20, (result) => console.log(result));

// cb에 return문을 작성해도 에러가 뜨지 않는데
// callback 타입에 void를 지정하면 반환할 수 있는 모든 결과를 무시하기 때문임
addAndHandle(10, 20, (result) => {
  console.log(result);
  return result; // 값을 반환하는 작업 수행 x
});

콜백 함수는 자신이 전달되는 인수가 반환 값을 기대하지 않는 경우에도 값을 반환할 수 있음

The "unknown" Type

사용자가 무엇을 입력할지 알 수 없기 때문에 any와 다름

let userInput: unknown;
let userName: string;

// 에러 발생없이 어떤 값이든 저장할 수 있음. 컴파일에러x
userInput = 5;
userInput = 'la';

// unknown 타입은 string 타입에 할당할 수 없음
// userName = userInput;
// userInput을 any로 바꾸면 에러가 사라짐

// unknown은 any보다 좀 더 제한적

if (typeof userInput === 'string') {
  userName = userInput;
}

언제든 사용할 만한 타입은 아님

The "never" Type

아무것도 반환하지 않은다는 것을 확실히하기 위해 명시적으로 설정. 이런 작업을 수행함으로써 코드 품질의 관점에서 의도를 더 분명히 할 수 있음

// 에러 객체를 생성하여 넘기는 유틸리티 함수
function generateError(message: string, code: number): never {
  throw { message, errorCode: code }; 
}

아직은 잘 모르겟음..

공식문서 읽기
https://www.typescriptlang.org/docs/handbook/basic-types.html


The TypeScript Compiler (and its Configuration)

지금까지 tsc를 입력해 컴파일했지만 큰 프로젝트에 사용하기 적합하지 않음. 변경사항이 있을 때마다 이 커맨드를 실행하기 불편

Using "Watch Mode"

watch mode를 사용하면 TS가 파일을 관찰하고 변경사항이 있을 때마다 다시 컴파일함

$ tsc app.ts --watch
or
$ tsc app.ts --w

Don't quit this watch mode process whilst developing. you can quit thereafter via CTRL+C

규모가 큰 프로젝트에선 보통 사용하지 않음

Compiling the Entire Project / Multiple Files

관찰 모드는 훌륭한 기능이지만 만약 타입스크립트 파일이 두 개 이상이라면?
tsc --init
이 커맨드가 실행되는 폴더의 모든 항목을 알려주는 역할. 따라서 올바른 폴더의 위치에서

tsc 커맨드 실행하면 이 프로젝트의 모든 유형의 파일을 컴파일
tsc --w 관찰 모드로 결합 --w or --watch

36. Including & Excluding Files

Setting a Compilation Target

Understanding TypeScript Core Libs

More Configuration & Compilation Options

Working with Source Maps

rootDir and outDir

Stop Emitting Files on Compilation Errors

Strict Compilation

profile
꾸준히 자유롭게 즐겁게

0개의 댓글