03 객체와 타입

YoungMinKim·2021년 2월 21일
0
post-thumbnail

Goal

01, 02장은 TypeScript를 사용하기 위한 기본 셋팅 및 환경설정을 설명하고 있다.
03장 부터는 TypeScript에서 사용되는 기본 문법에 대해 정리 해보자.

03-1 타입스크립트 변수 선언문

let과 Const 키워드

ES5 자바스크립트는 variable의 앞 세 글자를 딴 var라는 키워드를 사용해 변수를 선언할 수 있다.
하지만 ESNext(ES6)는 var를 사용하지 말라 권고합니다. 다음은 EsNext 자바스크립트에서 let 키워드로
변수를 선언하는 방법입니다. let으로 선언한 변수는 코드에서 그 값이 수시로 변경될 수 있음을 암시한다.

let 변수 이름 = 초기값;

다음은 Const 키워드로 변수를 선언하는 방법입니다. const는 변수를 선언할 때는 반드시 초기값을 명시해야 한다.
const 변수는 코드에서 변숫값이 절대 변하지 않는다는 것을 암시한다.

const 변수 이름 = 초기값;

타입 주석

타입스크립트는 자바스크립트 변수 선언문을 확장해 다음과 같은 형태로 타입 명시가 가능하다.
이를 타입 주석이라고 한다.

let 변수이름 : 타입 = 초기값;
const 변수이름 : 타입 = 초기값;

다음은 타입 주석을 붙여 변수를 선언한 예,
만약 타입 주석에 선언한 타입과 우측에 있는 값의 타입이 같지 않으면
에러를 발생 시킵니다. 또한 TypeScript는 이름에서 알 수 있듯이 타입에 강한 제한을 두는 것으로 알고 있다.

let n : number = 1;
let b : string = "hello";
let s : boolean = true;
let o : object = {};

타입 추론

쉽게 말해 let : number와 같이 타입을 선언할 수 있는데, 해당 타입 주석 없이
값을 할당할 경우 우측의 값을 통해 해당 변수의 타입을 초기화 한다.
그냥 ES5 자바스크립트에서 타입 주석 없이 사용하지 않았는가, 위 부분을 생각하면 쉬울 것이다.

// 컴파일러가 우측의 값을 보고 좌측의 변수 값을 초기화
let a = 1;
let b = "string";
let c = true;
let d = new Object(); 

any 타입

any 타입은 타입 주석의 한 종류로 어떤 타입도 올 수 있다는 것을 의미 한다.
하지만 개인적으로 아래와 같이 선언을 해서 사용을 하지는 않을 것 같다.
변수 하나에 여러가지 값을 넣어 사용하는 것 자체가 👎

let num : any = "1";
num = 1;
num = true;
num = new Object();

undefined 타입

자바스크립트에서 undefined이다.
변수를 초기화하지 않으면 해당 변수는 undefined값을 가지게 된다.
하지만 타입스크립트에서의 undefined타입이기도 하고 이기도 하다.
JavaScript → undefined :
TypeScript → undefinded : 타입,

let u : undefined = undefined
u = 1; // Type '1' is not assignable to type 'undefined' 오류 발생

ESNext 타입스크립트의 타입 상속 관계를 보면 any가 모든 타입의 루트 타입, 즉 최상위 타입이다.
반면에 undefined는 모든 타입의 최하위 타입이다.
쉽게 말해 부모 타입을 자식 타입에 넣을수는 없지 않은가?

템플릿 문자열

타입스크립트에는 변수에 담긴 값을 조합해 문자열을 만들 수 있게 하는 템플릿 문자열을 제공 한다.
이 구문은 역 따옴표 ${}로 문자열을 감싸는 형태로 사용이 된다.
솔직히 Java, JavaScript를 주로 사용하는 필자는 '+'가 편한 것 같다.

`${변수 이름}`

실제로 사용을 하면 어떤 형태인지 확인 해보자.
JSP 템플릿 엔진을 사용할때와 비슷한 것 같은데 많이 사용이 될지 모르겠다.

let count = 10, message = "Your Count";
let result = `${message} is ${count}`;
console.log(result);

03-2 객체와 인터페이스

인터페이스 선언문

타입스크립트는 객체의 타입을 정의할 수 있게 하는 interface라는 키워드를 제공한다.

  • 객체(Object)의 타입을 정의하는 목적.
  • 중괄호 {}로 속성과 속성의 타입 주석을 나열.
interface 인터페이스이름 {
	속성 이름[?]: 속성 타입[,...]
}
interface IPerson {
	name: string
	age: number
}

인터페이스 조건을 벗어나는 예를 아래의 코드를 통해 알아보자.

interface IPerson {
	name: String
	age: number
}

let good : IPerson = {name : "Jack", age : 23};

let bad1 : IPerson = {name : "Jack"}; // age속성이 없기에 error
let bad2 : IPerson = {age: 32}; // name속성이 없기에 error
let bad3 : IPerson = {}; // name, age 속성이 둘다 없기에 error
let bad4 : IPerson = {name : "Jack", age : 32, etc : true}; // etc속성은 없는데? error

선택 속성 구문

인터페이스 설계 시 어떤 속성은 반드시 있어야 하지만, 어떤 속성은 있어도 되고 없어도 되는
형태로 만들고 싶을때가 있다. 이러한 속성을 선택 속성이라고 한다.

아래 코드를 보면 IPerson2의 마지막 속성 etc를 초기화하지 않은 let good 변수가 있다.
하지만 해당 etc 속성은 선택 속성으로 결정이 되었기에 error를 반환하지 않는다.

interface IPerson2 {
	name : string
	age : number
	etc? : boolean // 선택 속성
}

let good : IPerson2 = {name : "Jack", age : 32};
let good2 : IPerson2 = {name : "Jack", age : 32, etc : true};

익명 인터페이스

중간 복습 : 타입스크립트에서 인터페이스는 객체(Object)의 속성 타입의 범위를 제한하기 위해 사용이 된다.

타입스크립트는 interface 키워드를 사용하지 않고 인터페이스의 이름도 없는
인터페이스를 만들 수 있다. 이를 익명 인터페이스라 한다.

다음은 익명 인터페이스의 예를 보여주는 코드다.
익명 함수는 사용해봤어도, 익명 인터페이스라...?

// 01
let ai : {
	name : string
	age : number
	etc? : boolean
} = {name : "Jack", age : 32};

// 02 위 코드를 푼 것
let ai : { name : string age : number etc? : boolean } = {name : "Jack", age : 32};

익명 인터페이스는 주로 함수구현할 때 사용이 된다.

// me : Object로 바라보자
function printMe(me : {name : string, age : number, etc? : boolean}) {
		console.log(
			me.etc ? 
				`${me.name} ${me.age} ${me.etc}` : 
				`${me.name} ${me.age}`
		)
}

printMe(ai);

03-3 객체와 클래스

이번 챕터에서는 Java와 비슷한 부분이 많기에 개인적으로 알고 있는 내용은 넘어가겠다.
즉, Java와 비교를 해가는 형식으로 진행을 해보자.

TypeScript 클래스 선언문

// 01
class Person1 {
	name: string
	age : number
	
	constructor(name : string, age : number) {
		this.name = name;
		this.age = age;
	}
}

let person : Person1 = new Person1("Jack", 32);
console.log("name : " + person.name);
console.log("age : " + person.age);

// 02 타입스크립트는 생성자의 매개변수에 public과 같은 접근 제한자를 붙히면 해당 매개변수의 이름을 가진
// 속성이 클래스에 선언된 것처럼 동작합니다. 즉 변수 선언이 되있는거랑 똑같은 듯
class Person2 {
	constructor(public name : string, public age? : number) {

	}
}

let person2 : Person2 = new Person2('Jack', 32);
console.log("person2 : " + person2); // Person2 : {name : 'Jack', age : 32}; 

02번에서 말했다시피 생성자의 매개변수에 접근제한자(public, private, protected, default)가
선언이 되면, 해당 매개변수의 이름을 가진 속성(변수?)이 클래스에 선언된 것처럼 동작한다.

추상 클래스

클래스들의 공통되는 필드메소드정의한 클래스를 말한다.

추상 클래스 및 메소드 특징

1. 자체적으로 객체를 생성할 수 없다. 따라서 상속을 통해 자식 클래스에서 인스턴스를 생성해야 한다. 
2. 추상 클래스는 추상 메소드, 일반 메소드, 필드(멤버변수), 생성자로 구성된다. 
3. 일반적인 상속의 특성과 동일하다.(extends 이용, 단일 상속, 생성자 호출 등) 
4. 추상 클래스를 상속받는 클래스는 추상 메소드를 반드시 오버라이딩(overriding) 해야 한다.     
오버라이딩 할 때 abstract를 제외한 시그니처를 클래스에서 동일하게 적어줘야 한다.

추상 클래스 사용 이유

우선 추상 클래스를 사용하는 이유를 알기 전에 간단한 특징을 알아보자.

  1. 자체적으로 인스턴스 생성 불가능.
  2. 추상 클래스 상속 시 반드시 추상 메서드를 구현 하여, 오버라이딩 하여야 한다.
  3. 일반 extends 키워드와 동일하게 사용이 된다.
    1. 일반 메서드 선언.
    2. 필드명 선언.
    3. 생성자 선언.

자체적으로 인스턴스를 생성하지도 못하고 시그니처만 있는 메소드를 왜 만들어서 상속을 강제적으로 받고
오버라이딩을 해야 하는 것일까? 작은 프로젝트에서는 추상 클래스를 사용하는 일은 거의 없을 것이다.

하지만 프로젝트가 커지면 여러 개발자가 참여하게 된다. 이때 공통적으로 작성되어야 할 내용들이 있을 것이다.
이러한 내용들을 개발자들마다 이름(메소드명, 필드명 등)을 다르게 정의한다면 유지보수관리 등 문제가 발생될 것이다.

따라서 추상 클래스를 사용하는 이유는 공통된 내용을 추출하여 통일된 내용으로 작성하도록 규격화하는 것이다.
상속받은 클래스들은 자기 클래스의 필요한 메소드나 필드만 추가로 정의하고, 추상 메소드를 오버라이딩하여
클래스마다 다르게 실행될 로직을 작성해 주면 된다. 

예를 들어 iPhone, 갤럭시 등은 스마트폰(추상 클래스)이다.
이것들의 공통적인 내용(일반 메소드, 필드)들은 전화를 하거나 인터넷 검색을 하고, sns, 게임 등을 할 것이다.

자동차(추상 클래스 선언)

  • 추상 메서드

    • 시동을 건다(공통적인 부분) : void startEngine();

    • 달린다(공통적인 부분) : void run();

      • SM5
      • 그랜져
      • 람보르기니
      • 페라리

하지만 기기마다 색상, 제조사, 스펙 등 명칭은 같지만 다른 내용(추상 메소드)을 갖는다.
따라서 각 기기마다 다른 세부내용의 설명(추상 메소드 오버로딩)이 필요하다.

추상 클래스의 예(TypeScript)

abstract class AbstractPerson {
	abstract name : string // 얘는 하위 클래스에 반드시 존재하거나, 구현이 되어야 한다?
	constructor(public age?: number) {}
}

class Person5 extends AbstractPerson {
	constructor(public name : string, age? : number) {
		super(age); // 부모 생성자 호출
	}
}
let jack5 : Person5 = new Person5('Jack', 23);
console.log(jack5); // Person5 { name : 'Jack', age : 23}

Static(클래스 변수) 속성

타입스크립트에서 사용되는 static 속성은 Java에서 사용이 되는 static의 특징과 동일한 것 같다.

Static 장점

1. 메모리를 효율적으로 사용할 수 있다.

생성할 때마다 인스턴스가 힙에 올라가는 것이 아니라 고정 메모리이므로 효율적이다.

2. 속도가 빠르다.

객체를 생성하지 않고 사용하기 때문에 속도가 빠르다.

Static 클래스 변수 예

class A {
	static initValue = 1;
}

let initVal = A.initValue; // 1 출력

03-4 객체의 비구조화 할당문

03 챕터에서 현재 이 부분이 가장 난해하고 이해가 잘 안되는 것 같다.

구조화가 필요한 코드 예

let personName = 'Jack';
let personAge = 32;

let companyName = 'Apple Company, Inc';
let companyAge = 43;

만약 내가 Java를 사용한다면 아래와 같이 구현을 했을 것 같다.
IDE를 사용하지 않고 손코딩으로 써봤기 때문에 컴파일 안될 수 있음.

abstract class Parent {
	private String name;
	private String age;

	abstract public void getName() { }
	abstract public void getAge() { }
}

class Child extends Parent {
	
	@override
	public void getName() {
		....
	}

	@override
	public void getAge() {
		....
	}
}

그렇다면 타입스크립트에서 위 코드를 구조화 하려면 어떻게 해야 될까?
아래의 코드를 통해 구조화에 대해 생각 해보자.

export interface IPerson {
	name : string
	age : number
}

export interface ICompany {
	name : string
	age : number
}

구조화를 수행한 인터페이스를 타입으로 선언하여 아래와 같이 사용을 한다.

import {IPerson, ICompany} from './IPerson_ICompany'

let jack : IPerson = {name : 'Jack', age : 32},
		jane : IPerson = {name : 'jane', age : 32};

let apple : ICompany = {name : 'Apple Computer, Inc', age : 43},
		ms : ICompany = {name : 'Microsoft', age : 44};

비구조화란?

구조화된 데이터는 어떤 시점에서 데이터의 일부만 사용해야 할 때가 있다.
이처럼 구조화된 데이터를 분해하는 것을 비구조화(destructuring)라고 한다.

구조화 예제 코드

// 구조화된 객체
let userInfo : IPerson = {
	name : 'Kim Young Min',
	age : 32
}

// 비구조화 수행
let userNaem = userInfo.name; // 이름 추출
let userAge = userInfo.age;   // 나이 추출
console.log("name : " + name);
console.log("age : " + age);

비구조화 할당 예제

import {IPerson} from './IPerson_ICompany';

let jack : IPerson = {name : 'Jack', age : 32};
let {name, age} = jack;
console.log(name, age);
  1. IPerson 인터페이스형 변수 jack을 선언.
  2. 현재 jack은 Object 형태로 name, age 속성(Property)를 가지고 있다.
  3. let {name, age} = jack;
    1. 위 같은 선언을 비구조화 할당이라 한다.
    2. 만약 구조화된 데이터의 데이터가 100개일 경우 일일이 다 받아오기에는 무리가 있기에 위 같은 기능을 제공.
    3. jack 객체(Object)가 가지고 있는 속성명을 통해 값(value)를 가져올 수 있다.
    4. 또한 name, age는 새로운 변수가 생성된다 생각하면 된다.

잔여 연산자

ESNext 자바스크립와 타입스크립트는 점을 연이어 3개 사용하는 ...연산자를 제공한다.
위 연산자는 사용하는 위치에 따라 잔여 연산자 혹은 전개 연산자라고 부른다.

잔여 연산자

  • 비구조화 할당을 통해 나머지 데이터를 가져올때 사용이 된다.
let address : any = {
	country: 'Korea',
  city: 'Seoul',
  address1: 'Gangnam-gu',
  address2: 'Sinsa-dong 123-456',
  address3: '789 street, 2 Floor ABC building'
}

const { country, city, ...detail} = address;
console.log(detail);

// 실행 결과
{
	address1: 'Gangnam-gu',
  address2: 'Sinsa-dong 123-456',
  address3: '789 street, 2 Floor ABC building'
}

전개 연산자

  • 여러개의 객체를 하나의 객체로 연결할 때 사용이 된다.
  • 비구조화 할당문이 아닌 곳에서 사용될 때 이를 전개 연산자라고 한다.
let part1 = {
        name: 'jane'
    },
    part2 = {
        age: 22
    },
    part3 = {
        city: "seoul",
        country: "Kr"
    }

let merged = {
    ...part1,
    ...part2,
    ...part3
};
console.log(merged);

타입 단언(타입 변환)

(<타입> 객체)
(객체 as 타입)

타입 단언 예제를 아래의 코드를 통해 살펴보자.

interface INameable {
	name: string
}

let obj : object = {name : 'Jack'}; // object에는 name속성이 존재하지 않는다.

let name1 = (<INameable> obj).name;
let name2 = (obj as INameable).name;
console.log(name1, name2);

이처럼 타입 단언의 두 가지 구문은 서로 형태만 다를 뿐 내용상으로는 같다.

참고 서적 및 자료

  1. http://m.yes24.com/Goods/Detail/89328106 - : Do it TypeScript
  2. https://jeong-pro.tistory.com/148 - : static
  3. https://m.blog.naver.com/PostView.nhn?blogId=heartflow89&logNo=220963055326&proxyReferer=https:%2F%2Fwww.google.com%2F : 추상클래스
profile
https://ym1085.github.io

0개의 댓글