TypeScript

김종현·2023년 3월 29일
0

Ts

목록 보기
1/2

타입 스크립트의 기본

  1. 개요
    -Js의 상위 집합, Js의 한계를 해결하기 위해 개발
    -Ts를 Js에서 사용하기 위해서는 컴파일 과정이 필요

(1) 쓰는 이유
ⓐ 동적 타입을 정적으로 선언
-동적 타입(Dynamic Typing) : 컴파일 단계 없이 인터프리터와 같은 코드 해석기가 코드를 라인 단위로 읽으면서 바로 코드를 실행할 때 코드 내의 변수 타입이 정해지는 방식

  • 스크립트 언어들이 흔히 많이 사용. Js, 파이썬, 루비

-정적 타입(Static Typing) : 타입을 코드에 명시적(일부는 묵시적)으로 표현하는 방식으로써 코드가 실행되기 전에 타입을 정의. 언어의 전용 컴파일러가 컴파일 단계에 코드 내의 변수들의 타입들을 분석하여 코드를 실행하기 전에 기초적인 결함을 찾을 수 있음

  • 컴파일 과정을 반드시 거쳐야하는 단점이 있으나 그만큼 기본적인 결함은 없는 결과물을 얻을 수 있는 장점이 존재. 컴파일러의 코드 리팩터링과 같은 최적화가 되어있어서 작성한 코드의 성능을 올려준다.

ⓑ 타입 유추를 통한 타입 제어 가능
ⓒ 컴파일 시점에서 오류 포착
ⓓ Js에서 찾을 수 없는 추가 코드 제공

1) 동적 타입 정적 선언

let a: number;
a=1;
a='b';
//Tpye 'string' is not assignable to type 'number'.ts(2322)

2) 타입 유추를 통한 타입 제어

//js
const sum = (a,b)=>{ return a+b}

sum(1, "2") // 12

//ts
const sum = (a: number, b: number) =>{return a+b}

sum(1, 2) // 3

(2) 자바 스크립트의 한계
-최초에 작은 규모의 개발 목적으로 설계
-대규모 프로젝트 도입 시 많은 문제들이 발생 : 웹 FE는 오직 Js만 가능
-코드 직관성, 고질적인 동적 타입 문제
-예측 불가능한 null, undefined 문제, reference to undefined...

// 코드 직관성
function foo(person) {
// person이 객체인지 string타입인지 판단하기가 어려움
// 만약 객체라면 안에 있는 property가 무엇인지도 현재 함수 코드만 봐서는 알 수가 없음
}

// 동적 타입의 문제
function foo() {
let a = 1;
let b = '2';
console.log(a + b); // 12
}
// 예측할 수 없는 null, undefined 문제
function foo(obj){
  console.log(obj.name); // Max
  console.log(obj.key.name); // reference to undefined property name
}

foo({
  id:1,
  name:"Max",
});
// API콜 캐싱 예시
const cache = new Map();

// 아래 코드에서 잘못된 점을 찾으시오
// ->반환값 통일이 안 되어 있음.
function getSomethingFromRemote(url) {
  if (cache.has(url)) {
    return cache.get(url);
    //Promise가 아님
  }
  if (url !== undefined && url !== null && url.length > 0) {
    return fetch(url)
      .then((response) => response.json())
      .then((result) => {
        cache.set(url, result);
        return result;
      }); // 얘는 promise를 리턴
  }
  return new Error("URL이 있어야합니다.");
}

// 아래 코드에서 잘못된 점을 찾으시오
// 반환값이 다 다름. 문자열, 숫자를 리턴했는데 마지막에 키 뱉으라하면 어떡함?
function wrongFunction(key) {
  if (key === "key") {
    return `This is your key: ${key}`;
  } else if (key === 1) {
    return 1234;
  }
  return key;
}

2. 기본 Type

-Js코드에 변수나 함수 등 Type을 정의할 수 있음
-Tpye을 나타내기 위해 타입 표기(Type Annotation)을 사용
-Ts의 type

  • 기본 자료형(primitive type), 참조 자료형(reference type), 추가 제공 자료형

(1) 기본 자료형
-객체와 참조 형태가 아닌 실제 값을 저장하는 자료형
-string, boolean, number, null, undefined, symbol(ES6 추가)

  • number : Ts에서 모든 숫자는 부동 소수점 수. 2/8/16진수 사용 가능.

(2) 참조 자료형
-객체, 배열, 함수 등과 같은 객체 형식 타입
-메모리에 값을 주소로 저장, 출력 시 메모리 주소와 일치하는 값을 출력
-object, array, function

(3) 추가 제공 자료형

-Ts에서 개발자 편의를 위해 추가로 제공하는 타입
-tuple, enum, any, void, never

1)tuple
-길이와 각 요소의 타입이 정해진 배열을 저장하는 타입

let arr : [string, number] = ["hi", 6];

arr[1].concat("!");
// 1번 인덱스에 string 전용 함수 사용시 에러 
// Error, 'number' does not have 'concat'

arr[3] = "hello";
// 정의하지 않은 index 호출 시 오류
// Error, Property '3' does not exist on type'[string, number]

2)enum
-특정 값(상수)들의 집합을 저장하는 타입
-여러 값들에 미리 이름을 정의하여 열거해 두고 사용

-JS 객체와의 차이점

  • enum은 한번 생성되면, 속성 추가 및 수정이 불가하다. 객체는 생성 이후에도 속성을 추가하고 변경할 수 있다.
  • enum은 속성 값으로 숫자, 문자열만 할당할 수 있다. 객체는 온갖 것을 다 넣을 수 있다.
enum Car { 
    BUS = "bus", 
    TAXI = "taxi", 
    SUV = "suv" 
}
const taxi:Car = Car.TAXI;

enum은 주로 아래의 2가지 경우에 사용

[1]분야별로 종류를 정의하여 명확하게 사용하기 위해서.
예를들어 사용자 권한을 관리자=0, 회원=1, 게스트=2 로 관리할 경우
개발자는 관리자 === 0 이라는걸 기억하고 코딩해야 한다.
하지만, 관리하는 값이 늘어나면 실수가 발생하기 쉽다.

enum Auth {
    admin = 0, // 관리자
    user = 1,  // 회원
    guest = 2  // 게스트
}
// 관리자 여부를 숫자로 체크한다.
if (userType !== 0) {
    alert("관리자 권한이 없습니다");
}

혹은 게시물의 종류를 관리할 때도 게시물 종류만 모아서 enum으로 정의 해두고 사용하면 편리하다

// 게시물 종류를 enum으로 관리
enum Articles {
    notice,  // 0: 공지사항 (값 생략시, 0 할당됨)
    board,   // 1: 일반글 (이전값에 1을 더한 1이 할당됨)
    comment  // 2: 댓글 (이전값에 1을 더한 2가 할당됨)
}

// 게시물 등록 분기 처리에 enum 사용
switch(articleType){
    case Articles.notice:
        // DB에 공지로 등록 작업
        break;
    case Articles.board:
        // DB에 일반글로 등록 작업
        break;
    case Articles.comment:
        // DB에 댓글로 등록 작업
        break;
    default:
        break;
}

[2]하드코딩의 실수를 줄이기 위해서.
예를 들어 언어 종류를 정의해야하는 경우, 직접 'ko', 'en' 을 하드코딩하면 실수하기도 싶고,
'ko' 였는지, 'KO' 였는지, 'ko-KR' 이었는지 등을 매번 찾아봐야 한다.

하지만, enum으로 정의해두면 이런 하드코딩의 폐해를 막을 수 있다.

// 언어 종류 관리
enum Languages {
    korean = 'ko', // 문자열을 입력할 수도 있다.
    english = 'en',
    japanese = 'jp',
    chiense = 'cn'
}

// setLanguage("ko-kr") // 하드코딩은 이런 실수를 유발한다.
setLanguage(Languages.korean) // enum 사용시, 의미파악이 쉽고 자동완성

특징 ⓐ
-enum은 기본으로 0부터 1씩 증가하는 값을 갖는다.

enum Auth {
    admin, // 0 : 별도로 값을 지정해주지 않으면 0부터 시작한다
    user,  // 1 : 이전 값에 1씩 더해진다.
    guest  // 2 : 이전 값에 1씩 더해진다.
}

console.log(Auth.admin);  // 0
console.log(Auth.user);   // 1
console.log(Auth.guest);  // 2

특징 ⓑ
-숫자 값을 지정해줄 수 있다. 정의되지 않은 값은 이전 값에서 1씩 증가한다.

enum Articles {
    notice = 100, // 값을 직접 지정 가능
    board = 300,  // 값을 직접 지정 가능
    comment       // 자동으로 앞의 300에 +1 더해줌 => 301
}

console.log(Articles.notice);  // 100
console.log(Articles.board);   // 300
console.log(Articles.comment); // 301

특징 ⓒ
-문자열을 지정해 줄수 있다.

enum Languages {
    korean = 'ko',
    english = 'en',
    japanese = 'jp',
    chiense = 'cn'
}

console.log(Languages.korean);   // "ko"
console.log(Languages.english);  // "en"
console.log(Languages.japanese); // "jp"
console.log(Languages.chiense);  // "cn"

3)any
-모든 타입을 저장 가능, 컴파일 중 타입 검사를 하지 않음

let str: any = "hi";
let num: any = 10;
let arr: any = ["a", 2, true];

4)void
-보통 함수에서 변환 값이 없을 때 설정(return x), any의 반대타입
-변수에는 undefined와 null만 할당하고 함수에는 반환 값을 설정 할 수 없음.

let unknown: void = undefined;

function sayHi(): void{
  console.log("hi");
}

5)never
-발생할 수 없는 타입
-항상 오류를 발생시키거나 절대 반환하지 않는 반환 타입
-종료되지 않는 함수

function neverEnd() : never{
  while(true){}
}

//Error : Afunction returning 'never' cannot have a reachable end point
//무한 반복에 break를 주면 never 사용 불가
function neverEnd() : never{
  while(true){}
  break;
}

//항상 오류를 발생시키는 함수
function error(message:string):never{
  throw new Error(message);
}

3. Tpye alias와 Utility types

(1) Type alias
-타입을 직접 정의

let p1: {name:string, age:number} = {
  name: "kim",
  age: 13
};

-이와 같은 직접 정의는 번거롭고 오래 걸린다.

type Human = {
  name: string;
  age: number;
};

let p2:Human = {name: "cheshire", age:15};

-이와 같이 타입 자체에 이름을 붙어 변수를 만들 수 있다.

(2) Utility types

-이미 정의해 놓은 타입을 변환하고 싶을 때 사용하면 좋은 방법.
-유틸리티 타입을 이용하면 기존의 타입을 이용해 새로운 타입을 만들 수 있음.

-공통 타입 변환을 용이하게 함
-유틸리티 타입은 전역으로 사용 가능

1) Partial< T >
-프로퍼티를 선택적으로 만드는 타입을 구성
-주어진 타입의 모든 하위 타입 집합을 나타내는 타입을 반환

//
interface Todo{
  title:string;				
  description:string;
}
function updateTodo(todo:Todo, fieldsToUpdate:Partial<Todo>){
  return{...todo,...fieldsToUpdate};
}
const todo1 = {
  title:'책상 정리', 
  description:'책 꽂기',
};
const todo2 = updateTodo(todo1,{
  description:'쓰레기 버리기',
});

2) Readonly< T >
-프로퍼티를 읽기 전용으로 설정한 타입을 구성

3) Record< T >
-프로퍼티의 집합 K로 타입을 구성
-타입의 프로퍼티들을 다른 타입에 매핑시키는 데 사용

4) Pick< T, K >
-프로퍼티 K의 집합을 선택해 타입을 구성

5) Omit< T,k >
-모든 프로퍼티를 선택한 다음 K를 제거한 타입을 구성

6) Exclude< T,U >
-T에서 U에 할당할 수 있는 모든 속성을 제외한 타입을 구성한다.

7) Extract< T,U >
-T에서 U에 할당 할 수 있는 모든 속성을 추출하여 타입을 구성한다.

8) NonNullable< T >
-null과 undefined를 제외한 타입

9) Parameters< T >
-함수 타입 T의 매개변수 타입들의 튜플 타입을 구성

10) ConstructorParameters< T >
-생성자 함수 타입의 모든 매개변수 타입을 추출
-모든 매개변수 타입을 가지는 튜플 타입(T 함수가 아닌 경우 never)을 생성

11) ReturnType< T >
-함수 T의 반환 타입으로 구성된 타입을 생성

12) Required< T >
-T의 모든 프로퍼티가 필수로 설정된 타입을 구성

4. 함수 사용

(1) 함수 선언 방법
-함수 작성 시 반환 타입을 추론 하도록 하는 걸권장
-함수의 매개 변수와 인수의 타입이 호환 가능하게 작성
-인수의 타입을 잘못 전달하면 에러 발생
1) 함수 선언식

function world1(name : string):string{
	return `${name}`;
}

2) 함수 표현식

let world2 = function(name: string): string{
	return `${name}`;
}

3) 화살표 함수 표현식

let world3 = (name: string): string =>{
	return `${name}`;
}

4) 함수 생성자(되도록 사용권장 안함)

let world5 = new Function(A,B);

(2) 타입 추론
-Ts 컴파일러는 방정식의 한쪽에만 타입이 있더라도 타입 추론이 가능하다
-이러한 타입 추론 형태를 'contextual typing'이라고 한다.
-이를 통해 프로그램에서 타입을 유지하기 위한 노력이 줄어든다.

function add(a: number, b: number) {
    const res = a + b; 
    return res.toString();
}
//res 변수는 number, 반환 값은 string이라는 것이 명백하기 때문에 일일이 명시 x

5. 함수 매개변수

(1) 기본 매개변수
-함수에 주어진 인자의 수는 함수가 기대하는 매개변수의 수와 일치해야 한다.

function buildName(firstName: string, lastName: string){
  return firstName + " " + lastName;
}

let result1 = buildName("kim"); // Error : expected 2 arguments, but got 1
let reulst2 = buildName("kim", "dongjun");
let result3 = buildName("kim", "dongjun", "Mr."); // Error : expected 2 arguments, but got 3

(2) 선택적 매개변수(optional parameter)
-Js에서는 모든 매개변수가 선택적으로 사용 가능, 인수가 없다면 undefined가 된다.
-Ts에서도 모든 선택적 매개변수를 사용할 수 있다(변수명 뒤에 '?')
-선택적 매개변수는 파라미터 중 가장 뒤에 위치해야한다.

function buildName(firstName: string, lastName?: string){
  if(lastName) return firstName + " " + lastName;
  return firstName + " " + lastName;
  else return fristName;
}

let result1 = buildName("kim");
let reulst2 = buildName("kim", "dongjun");
let result3 = buildName("kim", "dongjun", "Mr."); // Error : expected 2 arguments, but got 3

(3) 기본-초기화 매개변수(default parameter)
-Ts에서는 값을 제공하지 않거나 undefined로 했을 때에 매개변수의 값 할당이 가능

function buildName(firstName: string, lastName = "jun"){
  //lastName default 값 = jun
  if(lastName) return firstName + " " + lastName;
  return firstName + " " + lastName;
  else return fristName;
}

let result1 = buildName("kim"); // 값 제공x, "kim jun"
let reulst2 = buildName("kim", undefined); // 값 undefined, "kim jun"

let reulst3 = buildName("kim", "dongjun"); // "kim dongjun"
let result4 = buildName("kim", "dongjun", "Mr."); // Error : expected 1-2 arguments, but got 3

(4) 나머지 매개변수(rest parameters)
-컴파일러는 생략 부호(...) 뒤의 인자 배열을 빌드해 함수에서 사용할 수 있다
-나머지 매개변수는 매개변수의 수를 무한으로 취급한다
-아무것도 넘겨주지 않을 수도 있다

-나머지 매개변수는 무조건 배열 형식.

-함수에 전달될 파라미터의 수가 정해져 있지 않을 경우, 파라미터의 값을 배열로 전달받는 방식
-마지막 매개변수에 위치해야한다.

function buildName(firstName: string, ...restOfName:string[]){
  return firstName + " " + restOfName.join(" ");
}

let result = buildName('park', 'kim', 'lee', 'choi');
//restOfName = ['kim', 'lee', 'choi']

Class

1. 객체 지향 프로그래밍(OOP)

-컴퓨터 프로그램을 객체의 모임으로 파악하려는 프로그래밍 패러다임
-객체들은 서로 메시지를 주고 받을 수 있으며 데이터를 처리할 수 있다

(1) 장점
-프로그램 유연화, 변경 용이, 개발/보수 간편, 직관적 코드 분석
-강한 응집력(strong cohsion)과 약한 결합력(weak coupling)을 지향

(2) Class 용어 설명

1) 클래스의 요소
-멤버/필드/생성자/메소드
-인스턴스 : new 연산자에 의해 생성된 객체

2) 클래스 생성
-new를 사용하여 Person 클래스의 인스턴스를 생성
-Person class 의 멤버는 name, constructor, say()
-클래스 내에서 this를 앞에 붙이면 클래스의 멤버를 의미.

class Person{
  	name: string;
	constructor(name:string){
      //컨스터럭터 자체 값은 타입 지정 안 함.
  		this.name = name;
      //클래스 내에 꺼내서 사용할 프로퍼티들을 모두 기입
      //컨스터럭터의 파라미터와 생성자의 파라미터의 존재가 동일해야함.
      //비어있다면 비우고, 파라미터를 받는다면 둘 다 받아야 함.
	}
	say() {
  return "hi i'm" + this.name;
	}
}

let person = new Person("kim");
person.say();

2. 접근 제어자/상속

-속성 또는 메소드로의 접근을 제한하기 위해 사용 : 클래스 멤버를 클래스 외부에서 허용 또는 차단할때 사용
-java와는 다르게 package 개념이 없어 default 접근 제어자는 존재하지 않는다.
-Ts에서는 3종류의 접근 제어자가 존재

  • public > protected > private

(1) public

-선언된 멤버에 대해 클래스 밖에서도 자유롭게 접근할 수 있습니다.
-Ts에서 멤버는 기본적으로 public으로 선언(default)
-명시적으로 멤버를 public으로 표시 가능

class Animal{
  public name:string
  //name 필드 public화
  constructor(theName:string){
    this.name=theName;
  }
}

new Animal("cat").name;
//name 필드가 public이므로 animal class 인스턴스(외부)에서 name 접근 가능

(2) private

-선언된 멤버에 대해 클래스 외부에서 접근 불가

class Animal{
  private name:string
  //name 필드 private화
  constructor(theName:string){
    this.name=theName;
  }
}

new Animal("cat").name; // Error : Property 'name is private and only accessible within class 'Animal'

(3) 상속

-OOP는 상속을 이용하여 존재하는 클래스를 확장해 새로운 클래스를 생성 가능

1) extends

-부모 클래스의 필드, 메소드, 타입을 자식 클래스가 물려 받으면서 새로운 기능, 필드도 추가.

2) 장점

-객체/필드/메소드 그리고 코드의 재사용
-중복을 줄여 코드 간결성 확보, 유지보수에 용이
-부모클래스가 잘 정의되었을 경우, 자식 클래스의 작성이 편하다
-계층 관계를 표현 : 만약 상속하지 않는다면, A 클래스와 B 클래스는 겹치는 코드가 많은데 어떤 연관성이 있는지, 확장된 개념인지 파악하기 어렵다.

3) super()

-부모 클래스의 멤버에 접근할 수 있는 키워드, 자식 클래스의 생성자 코드에서 사용
-생성자 뿐만 아니라, super.필드명, super.메소드명 으로 부모 클래스의 멤버에 접근할 수 있다.
-super 이용, 부모 클래스의 필드나 메소드를 재정의할 수 있다.

//super의 사용
class Animal{
  name: string;
  species: string;
  
  constructor(name: string, species: string){
    this.name = name;
    this.species = species;
  }
  bark(): void{
    console.log(`My name is ${this.name}`);
  }
}

class Puppy extends Animal{
  constructor(name: string, species: string){
    super(name, species);
    //super로 여러개 가져오는 형식은 이렇게.
  }
    //오버라이딩
  bark(): void{
    console.log(`I'm cute, I'm ${this.name}`);
  }
}

var myAnimal = new Animal("DDD");
myAnimal.bark();
var myPuppy = new Puppy("ddd");
myPuppy.bark();

ⓐ 오버라이딩(재정의)
-정상 작동 코드

//정상 작동 코드
class Base {
	greet() {
		console.log("Hello, world!");
	}
}

class Derived extends Base {
	greet(name?: string) {
		if (name === undefined) {
			super.greet();
		} else {
			console.log(`Hello, ${name.toUpperCase()}`);
		}
	}
}
const d = new Derived();
d.greet();
d.greet("reader");

-작동하지 않는 코드
-Derived 자식 클래스의 greet 메소드는 파라미터가 필수적으로 필요하게 재정의되어서, 부모
클래스의 greet 메소드에 위배되었다.

  • 부모 클래스의 greet()는 동일하지만 자식 클래스에서 아래와 같이 바뀌었다.
  • greet(name?: string) => greet(name: string)
  • 만약 자식 클래스에서 파라미터를 사용하고자 한다면 optional parameter로 지정해야한다 (?붙이기).
  • 하지만 이 경우에도 부모클래스의 계약을 위반하지 않아야 한다. 즉, 어느 때라도 부모 클래스의 계약을 위반하지만 않으면 된다.
//재정의 할 때는 부모의 return type이나 parameter 같은 계약을 깨면 안 된다.
class Base {
	greet() {
		console.log("Hello, world!");
	}
}
class Derived extends Base {
// Make this parameter required
	greet(name: string) {
/* Property 'greet' in type 'Derived' is not assignable to the same property in base type 'Base'.
Type '(name: string) => void' is not assignable to type '() => void'.
*/
		console.log(`Hello, ${name.toUpperCase()}`);
	}
}

-extends 키워드로 Animal이라는 기초 클래스에서 Dog 클래스 파생
-파생된 클래스는 하위 클래스(subclass) 기초 클래스는 상위 클래스(superclass)라고 한다
-하위 클래스는 상위 클래스의 기능(메소드)을 확장

  • 예를 들어 사람 클래스에 움직이는 기능이 있었다면, 어른과 노인 모두 똑같이 움직이는 기능을 확장하여 사용합니다. 다만, 둘의 움직이는 속도를 다르게 하는 등 세부적으로 변경
  • Dog, Cat은 Animal의 기능(move 메소드)를 확장하기 때문에, move()와 makeDound()를 가진 인스턴스를 생성할 수 있다
class Animal{
  move(distance: number){
    console.log('Animal moved ${distance}m.';
                }
}

class Dog extends Animal{
  makeSound(){
    console.log("멍");
  }
}

class Cat extends Animal{
  makeSound(){
    console.log("냥");
  }
}

const dog = new Dog();
dog.move(10);
dog.makeSound();

const cat = new Cat();
cat.move(5)
cat.makeSound();

(4) protected

-멤버가 포함된 클래스와 그 하위 클래스에서만 접근. 외부에서 x
-Person에서 파생된 Employee의 인스턴스 메소드에서는 name을 사용할 수 있다

class Person{
  protected name: stirng
  constructor(name: string){
    this.name = name;
  }
}

class Employee extends Person{
  private department: string
  
  constructor(name: string, department: string){
    super(name);
    this.department = department;
  }
  
  public getElvatorPitch(){
    return "my name is ${this.name}, i work in ${this.department}";
    //Employee는 Person의 하위 클래스이고 name은 protected 상태지만 내부 객체에서 this.name 사용 가능
  }
}

let kim = new Employee('kim', 'sales');
console.log(kim.getElevatorPitch());
console.log(kim.name); 
// Error : Property 'name' is protected and only accessible within class 'Person'and its subclasses
// Employee는 Person의 하위 클래스이지만 name은 protected 상태이므로 외부 객체에서 이런식으로 직접 사용은 불가능.      

3. Getters & Setters/ readonly/ static

(1) getters & setters

-비공개로 설정하려는 속성은 private로 설정하여 직접 호출하거나 수정하지 않고 대신 속성값을 읽고 수정하는 getter/setter 함수를 사용
-class 속성에 직접 접근하는 것을 막고 getter/setter 함수를 사용해 값을 받아오거나 수정한다.(속성에 직접 접근해 수정하면 데이터 무결성이 깨질 수 있다 - 캡슐화 권장)
-각 객체의 멤버에 접근하는 방법을 세밀하게 제어할 수 있다.

-클래스의 멤버에 잘못된 값을 넣어 에러를 발생시키는 것을 막기 위해 이용

  • 예를 들어 사람 클래스에 나이 속성이 있다고 할 때, 나이는 무조건 0보다 크기 때문에 getter / setter를 설정하여 0 이하의 값이 들어오는 것을 방지

-캡슐화 : 멤버에 직접적으로 접근하지 못하도록 하는 것

class Person{
  private _name: string
  
  get name(){
    return this._name;
    
  set name(name: string){
    if(name.length>10){
      throw new Error("too long name")
    }
    this._name = name;
  }
}
  
let person = new Person();

console.log(person.name);//undefined
person.name = 'kim';
console.log(person.name);//kim
person.name = 'kkkkkkkkkkkkkim'; // throw Error

(2) readonly

-읽기만 가능한 속성 선언, 변경 불가하게 만듦
-선언될 때나 생성자에서 값을 설정하면 이후 수정 불가
-변경될 경우가 없는 상수에 해당 키워드 이용.

  • const : 변수 참조, readonly: : 속성
class Person{
  readonly age: number =20 //선언 초기화
constructor(age:number){
  this.age = age;
	}
}

let person = new Person(10); //생성자 초기화
person.age=30; //Error : Cannot assign to 'age' beacuse it is a read-only property.

(3) static

-객체마다 할당되지 않고 클래스의 모든 객체가 공유하는 멤버인 전역 멤버를 선언할 때 사용. 따라서 인스턴스 없이도 클래스 자체에서 해당 변수에 접근 가능.
-전역 멤버 : 객체마다 할당되지 않고 클래스의 모든 객체가 공유하는 멤버

-범용적으로 사용되는 값에 설정한다.
-멤버를 정적으로 선언하며 '클래스명.'을 앞에 붙여 사용
-ES6 에서는 메소드 전용 속성에는 선언이 안되었으나 Ts에서는 사용할 수 있다

class Grid{
  static origin = {x:0, y:0}
  calculateDistanceFromOrigin(point: {x:number, y: number}){
    let xDist = point.x - Grid.origin.x; // Grid 내부에서 orgin 앞에 클래스명을 붙여 접근
    let yDist = point.y - Grid.origin.y; //
    return Math.sqrt(xDist*yDist + yDist*yDist)/this.scale;
  }
  constructor(public scale: number){}
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

//외부에서 접근 또한 가능, 값을 할당도 가능
// 외부에서 Grid origin 값 변경(초기화)
Grid.origin = { x:3, y:3 }class Grid2 {
  static origin = { x:0, y:0 }
  
  calculateDistance(): void {
    console.log(Grid.origin.x * Grid.origin.y);
  }
}

const grid3 = new Grid2();

// 외부에서 Grid origin 값 변경
grid3.calculateDistance(); // 0
Grid2.origin = { x:3, y:3 }
grid3.calculateDistance(); // 9

4. 추상 클래스

⇒ 하위 클래스(=구체 클래스)에서 공통 동작을 찾는 것
-추상 메소드도, 일반 메소드도 가질 수 있다.
-바로 객체로 만들 수 없다. ⇒ 이 추상 클래스를 extends 한 클래스 의 객체로 만들 수 있다.

(1) 추상 클래스가 필요한 상황

-공통적인 동작을 정의하기 위해서
-상속 받아서 기능을 확장할 때
-복제품으로 상속하게 만들고 싶을 때

(2) 추상 메소드

-자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드 → 모듈처럼 중복되는 부분이나 공통적인 부분은 미리 다 만들어진 것을 사용하고, 이를 받아 사용하는 쪽에서는 자신에게 필요한 부분만을 재정의하여 사용함으로써 생산성이 향상되고 배포 등이 쉬워진다.
-추상 클래스는 다른 클래스들이 파생될 수 있는 기초 클래스로 직접 인스턴스화를 할 수 없는 클래스 : 껍데기 메소드

  • 선언은 되어있지만, 구현은 되어있지 않다.
  • 메소드명, 파라미터, 접근제어자(public, … ), 반환 타입은 정의되어 있다.
  • 메소드의 처리 로직은 포함하지 않는다.

(3) abstract

-abstract 키워드를 사용하여 추상 클래스나 추상 메소드를 정의
-추상 메소드는 클래스에는 구현되어 있지 않고(상위 클래스에서 abstract 붙임) 파생된 자식 클래스에서 구현(하위 클래스에서 abstract 붙인 부분 키워드 떼고 구현)해야 한다

abstract class Animal{
  //추상 클래스 선언
  protected name: string
  
  constructor(name: string){
    this.name = name;
}

  abstract makeSound(): void
  //추상 메소드 선언
  
  move(): void{
    console.log("move!");
	}
}


class Dog extends Animal{
	constructor(name: string){
      super(name); 
}
  makeSound(): void{ 
    //반드시 파생된 클래스에서 추상 메소드 구현 필수, 미구현시 에러 발생.
    console.log(this.name+"멍멍");
  }
}
    
const animal = new Animal("animal");
// Animal은 추상 클래스이므로 직접 선언시 에러 발생
// Error : Cannot create an instace of an abstract class

const dog = new Dog("진돗개");

dog.makeSound(); //진돗개 멍멍

(4) 추상 클래스를 활용한 디자인 패턴(template method pattern)

-디자인 패턴 : 프로그램의 일부분을 서브 클래스로 캡슐화해 전체 구조를 바꾸지 않고 특정 단계의 기능을 바꾸는 것
-전체적인 알고리즘은 상위 클래스에서 구현하고 다른 부분은 하위 클래스에서 구현

1)템플릿 메소드 패턴
-기능의 구조만 메소드에 정의하고 하위 클래스에서 기능의 구조는 유지한 채 세부적인 알고리즘만 재정의 하는 디자인 패턴
-전체 구조는 유사하나 부분적으로 다른 구문으로 구성되어 상황에 맞춰 알고리즘 확장/변경 가능해서 코드 중복 최소화 가능
-추상 클래스를 이용해 상위 클래스에서 메소드의 구조를 잡고, 하위 클래스에서 디테일한 알고리즘을 변경함으로써 템플릿 메소드 패턴을 적용 가능

abstract class Parent(
  //템플릿 메소드: 자식에서 공통적으로 사용하는 부분(someMethod)
	public do(){
  		console.log("Parent에서 실행 - 1");
  
  		this.hook(); //훅 메소드: child에서 구현해야 할 부분
  
  		console.log("Parent에서 실행 - 2");
}
abstract hook():void
}

class Child extends Parent{
  hook(): void{
    console.log("child");
  }
}

const child = new Child();
child.do;
// Parent에서 실행 - 1, child, Parent에서 실행 - 2

TypeScript의 특성

다양한 타입

• any & unknown: 아무 타입 vs 알 수 없는 타입
• 원시 타입(primitives): number, string, boolean, null, undefined
• Array
• Function
• optional

1.class/interface

참고 :

(1) class는 설계도
-자동차 구성요소/기능 명세
-class는 개념뿐 아니라 개념을 '구현'해놓은 기능이 있기 때문에 생성(instantiate만 하면 된다.)

(2) interface는 컨셉(개념/관념)
-자동차란 무엇인가?
-inteface는 개념(타입)만 있기 때문에 개발자가 변수 및 함수를 직접 '구현'해야한다. 개념을 구체화 해서 만든다.
-보다 추상적인 개체를 표현할 때 사용하거나 복합적인 데이터 형태(composite data, 객체에 여러 데이터를 포장해서 담는 형식)를 타입으로 정의할 때 사용한다.

import crypto from "crypto";
/**
 * class & interface
 */
interface Car {
  serialNumber: string;
  type: "sedan" | "suv";
  name: string;
  run: () => void;
  stop: () => void;
  getType: () => string;
}

class Benz implements Car {
  serialNumber: string;
  name: string;
  type: "sedan" | "suv";

  constructor(name: string, type: "sedan" | "suv") {
    this.name = name;
    this.serialNumber = crypto.randomUUID();
    this.type = type;
    // 메소드의 this 바인딩은 보험 차원에서 해주는 것이 좋음
    this.run = this.run.bind(this);
    this.stop = this.stop.bind(this);
    this.getType = this.getType.bind(this);
  }

  run() {
    console.log(this);
    console.log(`${this.name}이 움직입니다.`);
  }

  stop() {
    console.log(`${this.name}이 멈췄습니다.`);
  }

  getType() {
    return this.type;
  }
}

const myCar = new Benz("S500", "sedan");

myCar.run();
myCar.stop();
  1. literal/ union/ intersection
/**
 * literal & union & intersection
 */
// 합집합은 OR, 교집합은 AND
type FavoriteAlcohol = "Wine" | "Whiskey" | "Beer";

const myAlcohol1: FavoriteAlcohol = "Wine";
const myAlcohol2: FavoriteAlcohol = "Soju"; // Wine, Whiskey, Beer중에 하나가 아니기 때문에 에러!

type Alchohol =
  | "Soju"
  | "Vodka"
  | "Makgolli"
  | "Wine"
  | "Whiskey"
  | "Beer"
  | "Cognac"
  | "Gin"
  | "Rum";

type Intersections = Alchohol & FavoriteAlcohol; // 교집합인 "Wine", "Whiskey", "Beer"가 뜬다.

interface A {
  type: string;
}

interface B {
  type: number;
}

type C = A & B;

const a: C = {
  type: 1, // string과 number의 교집합은 존재하지 않기 때문에 unknown 타입을 된다. 어떻게 고쳐주면 될까?
};
  1. type alias/ enum/ utility types

(1) type alias <영상보고 보충
-Type vs Interface => 객체의 형태를 정의하는 경우에는 interface를 사용하는 것이 좋은 경우가 많다.
-interface의 경우 merging이 일어남
-인터페이스와 비슷한 역할을 하지만, type alias는 확장(extends)가 불가능하고 인터페이스는 확장이 가능, 따라서 웬만하면 인터페이스 사용하는 것이 좋다.

type ID = string | number;

type Input = string;

type Point = {
  x: number;
  y: number;
};

...
interface A{
  name: string;
}
...
interface A {
  type: string;
}
//ts를 돌릴 경우 위의 투 인터페이스를 합침. -merging

(2) enum
-코드 수정시 매우 유용
-메소드 안됨, 값밖에 안됨.
-'='를 통해 값을 부여하지 않으면 0부터 자동으로 인덱스 붙음
-객체취급이기 때문에 interable

type Weather =
  | "Sunny"
  | "Cloudy"
  | "Rainy"
  | "Windy"
  | "Snowy"
  | "Typhoon"
  | "Hurricane";

enum Day {
  SUN = "sunday",
  MON = "monday",
  TUE = "tuesday",
  WED = "wednesday",
  THU = "thursday",
  FRI = "friday",
  SAT = "saturday",
}

function sayDays() {
  console.log(`Today is: ${Day.SUN}}`);
  console.log(`Today is: ${Day.MON}}`);
  console.log(`Today is: ${Day.TUE}}`);
  console.log(`Today is: ${Day.WED}}`);
  console.log(`Today is: ${Day.THU}}`);
  console.log(`Today is: ${Day.FRI}}`);
  console.log(`Today is: ${Day.SAT}}`);
}

(3) utility types

// utility type인 Record는 매우 유용하다. Object보다는 Record 타입이 더 유용 <  > 이건 제네릭, <key, value> 순서.
const dateToWeather: Record<number, Weather> = {
  1: "Sunny",
  2: "Cloudy",
  3: "Rainy",
  4: "Windy",
  5: "Snowy",a
  6: "Typhoon",
  7: "Hurricane",
};

function getWeather(date: number): Weather {
  return dateToWeather[date];
}
  1. 제네릭
    • 함수, 인터페이스, 클래스에 재사용성을 더해준 기능
    • 함수, 인터페이스, 클래스를 정의한 사람이 아닌 사용하는 사람이 필요한 타입을 제공하는 방식
    • 특정 구조를 가진 함수, 인터페이스, 클래스를 다양한 타입을 적용시킴으로 사용성을 극대화
    -재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징
import axios from "axios";

type IPInfo = {
  ip: string;
  network: string;
  version: string;
  city: string;
  region: string | null;
  region_code: string | null;
  country: string;
  country_name: string;
  country_code: string;
  country_code_iso3: string;
  country_capital: string;
  country_tld: string;
  continent_code: string;
  in_eu: boolean;
  postal: string | null;
  latitude: number | null;
  longitude: number | null;
  timezone: string | null;
  utc_offset: string | null;
  country_calling_code: string;
  currency: string;
  currency_name: string;
  languages: string;
  country_area: number;
  country_population: number;
  asn: string;
  org: string;
};

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const { data } = await axios(path);
  const { ip, network, ...rest } = data; // 보안상 ip는 제외F
  return rest;
}

async function main() {
  const data = await fetchApi<IPInfo[]>("https://ipapi.co/json");
  //받아오는 값이 IPInfo의 배열이라고 개발자가 명시.
  console.log(data);
}

main();

export {};
  1. 정적 타입 분석
    • tsc '' 명령으로 .ts 파일을 컴파일 함과 동시에 타입 분석을 실행함
    • 타입 분석 후 문제가 있을 시에는 에러 로그를 띄워줌
    • VSCode의 경우 현재 보고 있는 코드에 대한 실시간 타입 분석을 해주며 문제가 있을 시 빨간줄로 표시해줌
  2. 컴파일
    • tsc 명령으로 작성한 .ts 파일들을 컴파일 => js로 변경 +@
    • tsconfig.json 파일에서 다양한 컴파일 설정을 바꿔줄 수 있다: 컴파일 결과물 경로 변경, 컴파일 대상 경로, 컴파일 제외 경로
profile
나는 나의 섬이다.

0개의 댓글