[Typescript] 기본 문법 (1)

냥린이·2022년 1월 10일
0

Javascript/TypeScript

목록 보기
1/4

TS

TypeScript 한글 문서

✨ Class 선언하기

타입스크립트 튜토리얼 class

클래스의 기본 형식은 다음과 같다.

class myClass{
	consturctor() {
    }
}

클래스를 정의하는 class 키워드를 사용했다. constructor 생성자는 클래스가 객체로 생성될 때 기본적으로 호출되는 메소드이다.

TS를 JS로 변환하면 다음과 같다.

//TS
class class_name { //class scope }
//JS
var class_name = (function () {
   function class_name() {
   }
   return class_name;
}());

클래스 내부 구조를 좀 더 살펴보면 다음과 같은 요소가 있다.

  • fields : 필드는 클래스 안에서 선언되는 모든 변수다. 필드는 객체에 관련된 데이터를 나타낸다.
    - 필드를 선언할 때 var 키워드를 사용하지 않는다.
  • constructors : 클래스의 객체를 위한 메모리 할당을 한다.
    - this 키워드는 class의 현재 instance를 가리킨다. 파라미터 이름은 클래스의 필드 이름과 같다. 클래스의 필드는 this 키워드를 붙여서 인자값과 구분해준다.
  • functions : 객체가 해야 할 동작을 나타낸다. 때때로 메서드라고 부르기도 한다.
    - 함수를 선언할 때 function 키워드를 사용하지 않는다.

TS로 car라는 class를 선언한다.

//TS
class Car { 
   //field 
   engine:string; 
 
   //constructor 
   constructor(engine:string) { 
      this.engine = engine 
   }  

   //function 
   disp():void { 
      console.log("Engine is  :   "+this.engine) 
   } 
}

이를 JS로 바꾸면 다음과 같다.

//JS
var Car = (function () {
   //constructor
   function Car(engine) {
      this.engine = engine;
   }
	
   //function
   Car.prototype.disp = function () {
      console.log("Engine is  :   " + this.engine);
   };
   return Car;
}());

필드를 전역 변수로 선언할 때 구분을 위해 필드 앞에 _를 붙이기도 한다. 곧바로 생성자 매개변수를 전역화하고 싶다면 매개변수에 선언한 변수 앞에 public 접근자를 추가한다. public 접근자를 이용해 생성자의 매개변수가 아닌 클래스의 전역변수로 변수의 유효 범위가 확장된다.

class MyCar 
	constructor(public carName: string, public _numTier){}

	// 메소드
	getCarName(): string{
     		return this.carName;
   	}
	// 게터 함수
	get numTier(){ return this._numTier;}
}

✨ 객체 생성하기

클래스는 그 자체로 사용할 수 없으며 new 키워드를 통해 인스턴스를 생성하여 사용해야 한다. 표현식의 오른쪽 구문을 통해서 생성자를 호출한다. 객체에 할당하기 전에 new 키워드로 초기화를 하며 이때 생성자에 인자가 있다면 값을 전달해준다.

var myClass:MyClass = new MyClass();

클래스를 선언할 때는 선언자와 함께 객체를 담을 변수를 선언한다. 생략해도 무관하나 명시적인 타입을 통해 변수를 정의할 수도 있다.

var myClass:MyClass;
myClass = new MyClass();

선언과 객체 생성을 분리하는 것도 가능하다.

var object_name = new class_name([argument])

object_name.field_name
object_name.function_name()

객체이름과.으로 구분하여 class의 필드나 메소드에 접근한다.

Car 라는 Class를 할당 받은 객체 obj의 동작을 살펴본다.

class Car { 
   //field 
   engine:string; 
   
   //constructor 
   constructor(engine:string) { 
      this.engine = engine 
   }  
   
   //function 
   disp():void { 
      console.log("Function displays Engine is  :   "+this.engine) 
   } 
} 

//create an object 
var obj = new Car("XXSY1")

//access the field 
console.log("Reading attribute value Engine as :  "+obj.engine)  

//access the function
obj.disp()

결과

Reading attribute value Engine as : XXSY1 
Function displays Engine is : XXSY1

JS 로 변환

var Car = (function () {
   //constructor
   function Car(engine) {
      this.engine = engine;
   }
	
   //function
   Car.prototype.disp = function () {
      console.log("Function displays Engine is  :   " + this.engine);
   };
   return Car;
}());

//create an object
var obj = new Car("XXSY1");

//access the field
console.log("Reading attribute value Engine as :  " + obj.engine);

//access the function
obj.disp();

✨ Class 상속받기

class child_class_name extends parent_class_name
  • 자식 클래스는 extend 키워드를 통해 지정한 부모 클래스로부터 속성과 메소드를 상속 받는다. (JS에서는 prototype 이라는 기능을 사용해야 했지만 ES6부터 클래스 개념이 추가되었다.)
  • 이때 생성자나 private로 선언된 항목은 상속에서 제외된다.
  • TS는 다중 상속을 지원하지 않는다.
  • static 키워드를 통해 프로그램이 종료될 때까지 값을 유지할 수 있다. static 으로 선언된 속성이나 메소드는 [class_name] + . + [member_name]으로 참조된다.
  • instaceof 연산자를 통해 객체가 지정된 Type에 속한다면 true를 반환한다. (참고: 자바스크립트의 Type Checking)
  • 상속의 종류 (1) Single-level: 하나의 부모클래스로부터만 상속 받는다. (2) Multiple-level: 여러 클래스로부터 상속 받는다. 부모클래스의 부모클래스가 가진 속성이나 메소드에도 접근이 가능하다.
//TS
class Animal{
	protected constructor(public name: string, public leg: number){}
        getLeg(): number{
          return this.leg;
        }
        protected getName(): string{
          return this.name;	
        }
}
class Monkey{
	constructorb(name: string, leg: number){
    	super(name, leg);
    	}
        isClimbing(){
          return true;
        }
        superGetName(){
          return super.getName;	
        }
}
var monkey: Monkey=new Monkey("Lemur", 2)
console.log("원숭이 이름: "+monkey.name);
console.log("나무타기 가능 여부 : "+monkey.isClimbing);
console.log("다리 개수: "+monkey.getLeg());
console.log("상위 클래스의 메소드 호출 : "+monkey.superGetName);

결과

원숭이 이름 : Lemur
나무타기 가능 여부 : True
다리 개수 : 2
상위 클래스의 메소드 호출 : Lemur

✨ Method Overriding

class PrinterClass { 
   doPrint():void {
      console.log("doPrint() from Parent called…") 
   } 
} 

class StringPrinter extends PrinterClass { 
   doPrint():void { 
      super.doPrint() 
      console.log("doPrint() is printing a string…")
   } 
} 

var obj = new StringPrinter() 
obj.doPrint()
  • Overriding을 통해 자식클래스가 부모클래스의 메소드를 재정의할 수 있다.
  • super 키워드를 통해 부모클래스의 변수, 속성, 메소드를 가리킨다.

결과

doPrint() from Parent called… 
doPrint() is printing a string…

JS로 컴파일하면 다음과 같다.

var __extends = (this && this.__extends) || function (d, b) {
   for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
   function __() { this.constructor = d; }
   d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};

var PrinterClass = (function () {
   function PrinterClass() {
   }
   PrinterClass.prototype.doPrint = function () { 
      console.log("doPrint() from Parent called…"); 
   };
   return PrinterClass;
}());

var StringPrinter = (function (_super) {
   __extends(StringPrinter, _super);
	
   function StringPrinter() {
      _super.apply(this, arguments);
   }
	
   StringPrinter.prototype.doPrint = function () {
      _super.prototype.doPrint.call(this);
      console.log("doPrint() is printing a string…");
   };
	
   return StringPrinter;
}(PrinterClass));

var obj = new StringPrinter();
obj.doPrint();

✨ Encapsulation/Information Hiding

TS에 의해 지원되는 접근 수정자(접근 지정자)는 3개이다.

  • public
    • 어디서나 접근 가능
  • private
    • 정의된 클래스 내부에서만 접근 가능 (상속 X)
    • 외부에서 접근 시 컴파일 타임 에러 반환
  • protected
    • 정의된 클래스 내부 혹은 자식 클래스에서 접근 가능

✨ Interface

TypeScript - Interface | PoiemaWeb

  • 변수, 함수, 클래스에서 타입 체크를 위해 사용된다.
  • 여러가지 타입을 갖는 속성으로 이루어진 새로운 타입을 정의하는 것과 같은 효과이다. 인터페이스의 프로퍼티는 반드시 구현되어야 한다. (강제하고 싶지 않은 경우 프로퍼티 뒤에? 키워드를 붙여 선택적 프로퍼티로 선언할 수 있다.)
  • 인터페이스에 선언된 속성 및 메소드의 구현을 강제하며 일관성을 유지하도록 한다. 속성 및 메소드를 선언한다는 점에서 클래스와 유사하지만 모든 메소드는 추상 메소드이며 직접 인스턴스를 생성할 수 없다. 다만 abstract 키워드를 명시하지는 않는다.
  • ES6는 인터페이스를 지원하지 않지만 TS는 인터페이스를 지원한다. TS를 JS로 트랜스파일링하면 인터페이스는 삭제된다.
  • 인터페이스는 extend 키워드를 사용해 인터페이스 또는 클래스를 상속 받을 수 있다. 인터페이스는 여러개를 상속 받을 수 있다. (클래스는 안 된다.) 클래스를 상속 받을 때 모든 접근 지시자의 멤버가 상속되지만 구현은 상속되지 않는다.
  • 인터페이스는 참조 형식이므로 힙에 할당된다.
  • 인터페이스와 구조체가 유사하다고 느낄 수 있으나 구조체는 값 형식이므로 스택에 할당된다. TS에서는 type이 좀 더 구조체에 가까운 느낌이다. 에러가 발생했을 때 인터페이스는 인터페이스 이름을 보여주지만 type은 UUID 표현식의 우항을 보여준다.

(1) 변수에서 사용하기

// 인터페이스의 정의
interface Todo {
  id: number;
  content: string;
  completed: boolean;
}

// 변수 todo의 타입으로 Todo 인터페이스를 선언하였다.
let todo: Todo;

// 변수 todo는 Todo 인터페이스를 준수하여야 한다.
todo = { id: 1, content: 'typescript', completed: false };
  • 인터페이스를 통해 변수를 선언할 수 있다. 이때 인터페이스에 정의된 프로퍼티를 준수해야 한다.
// 인터페이스의 정의
interface Todo {
  id: number;
  content: string;
  completed: boolean;
}

let todos: Todo[] = [];

// 파라미터 todo의 타입으로 Todo 인터페이스를 선언하였다.
function addTodo(todo: Todo) {
  todos = [...todos, todo];
}

// 파라미터 todo는 Todo 인터페이스를 준수하여야 한다.
const newTodo: Todo = { id: 1, content: 'typescript', completed: false };
addTodo(newTodo);
console.log(todos)
// [ { id: 1, content: 'typescript', completed: false } ]
  • 변수에 인터페이스를 만들고 함수 내외부에서 사용한다.
  • 함수의 파라미터 타입을 인터페이스로 선언할 수 있다.
  • 함수 선언식에 파라미터와 타입을 다시 선언한다.
  • 인터페이스를 준수하는 객체를 만들어 값을 넣어준 후, 해당 변수를 함수의 파라미터로 활용한다.
  • 이렇게 하면 함수에 객체를 전달할 때 매개변수 체크를 별도로 하지 않아도 된다.

(2) 함수로 사용하기

// 함수 인터페이스의 정의
interface SquareFunc {
  (num: number): number;
}

// 함수 인테페이스를 구현하는 함수는 인터페이스를 준수하여야 한다.
const squareFunc: SquareFunc = function (num: number) {
  return num * num;
}

console.log(squareFunc(10)); // 100
  • 함수 인터페이스는 타입이 선언된 파라미터 리스트와 리턴 타입을 정의한다.

(3) 클래스로 사용하기

// 인터페이스의 정의
interface ITodo {
  id: number;
  content: string;
  completed: boolean;
}

// Todo 클래스는 ITodo 인터페이스를 구현하여야 한다.
class Todo implements ITodo {
  constructor (
    public id: number,
    public content: string,
    public completed: boolean
  ) { }
}

const todo = new Todo(1, 'Typescript', false);

console.log(todo);
  • implements 키워드를 사용해서 인터페이스를 가져오고 있는 클래스는 반드시 해당 인터페이스를 준수해서 구현해야 한다.
  • 인터페이스는 추상적인 규칙과 같은 것이다. 인터페이스는 직접 인스턴스를 생성할 수는 없지만, 인터페이스를 준수하여 구현하는 클래스들이 일관성을 갖도록 한다.
// 인터페이스의 정의
interface IPerson {
  name: string;
  sayHello(): void;
}

/*
인터페이스를 구현하는 클래스는 인터페이스에서 정의한 프로퍼티와 추상 메소드를 반드시 구현하여야 한다.
*/
class Person implements IPerson {
  // 인터페이스에서 정의한 프로퍼티의 구현
  constructor(public name: string) {}

  // 인터페이스에서 정의한 추상 메소드의 구현
  sayHello() {
    console.log(`Hello ${this.name}`);
  }
}

function greeter(person: IPerson): void {
  person.sayHello();
}

const me = new Person('Lee');
greeter(me); // Hello Lee
  • 인터페이스는 추상 메소드를 선언할 수 있다. 클래스는 인터페이스에 정의된 프로퍼티와 추상 메소드를 반드시 구현해야 한다.

✨ 추상 클래스

추상 클래스는 구현과 강제를 동시에 수행하는 클래스다. abstract 키워드를 이용해 선언하며, 추상 클래스 내부는 추상 메소드와 구현 메소드로 나누어 정의한다.

추상 메소드에도 동일하게 abstract 키워드를 붙이며 추상 클래스 안에서 선언만 되고, 실제 구현은 추상 클래스를 상속 받은 클래스 안에서 이루어진다.

abstract class SmallAnimals {
	abstract sound(): string;
  	abstract name(): string;
  	makeSound(): string{
      	// 추상메소드를 이용해 구현메소드의 로직을 추가
    	return `${this.name()} : ${this.sound()} `;
    }
}
class Mouse extends SmallAnimals{
	// 추상클래스에 정의된 추상메소드를 구현
  	// 구현한 메소드의 실제 구현 방식은 추상클래스 내부의 구현 메소드가 결정
  	sound(): string {
    	return "peep";
    }
  	name(): string {
      	return "mouse";
    }
  	// 클래스 고유의 메소드나 변수 추가
}
var mouse = new Mouse();
console.log(mouse.makeSound());

✨ Type Checking (feat. Duck Typing)

  • TS에서는 인터페이스에서 정의한 프로퍼티나 메소드를 가지고 있다면 그 인터페이스를 구현할 것으로 인정한다.
  • 이것을 structural typing(구조적 타이핑) 혹은 duck typing(덕 타이핑) 이라고 한다.

(1) 함수 예시

interface IDuck { // 1
  quack(): void;
}

class MallardDuck implements IDuck { // 3
  quack() {
    console.log('Quack!');
  }
}

class RedheadDuck { // 4
  quack() {
    console.log('q~uack!');
  }
}

function makeNoise(duck: IDuck): void { // 2
  duck.quack();
}

makeNoise(new MallardDuck()); // Quack!
makeNoise(new RedheadDuck()); // q~uack! // 5
  • 위 코드에서 RedheadDuck 클래스는 IDuck 인터페이스의 quack 메소드를 가지고 있지만 implements 키워드를 통해 인터페이스를 구현하지 않았다.
  • 그러나 IDuck 인터페이스를 인자로 받는 makeNoise 함수에 RedheadDuck 이 전달되어도 에러가 나지 않는다.

(2) 변수 예시

interface IPerson {
  name: string;
}

function sayHello(person: IPerson): void {
  console.log(`Hello ${person.name}`);
}

const me = { name: 'Lee', age: 18 };
sayHello(me); // Hello Lee
  • 변수 me는 IPerson 인터페이스를 구현하지 않았다. (변수나 함수에서 인터페이스를 구현하려면 : 연산자를 통해 명시해야 한다.)
  • 그러나 IPerson의 프로퍼티를 갖고 있기 때문에 인터페이스에 부합하는 것으로 인정된다.

✨ Decorator

TypeScript 한글 문서

function decorator_name(target){ // }

@decorator_name
function test(){}
  • 데코레이터는 @expression 로 표현되며, 이때 expression은 함수이며 런타임에 호출된다.
  • tsconfig.json 에서 experimentalDecorotor 를 true로 선언해서 사용할 수 있다. (설정을 안 하면 다음 릴리즈에서 지원하지 않을 수 있다는 경고가 뜬다.)
  • 클래스, 메소드, 프로퍼티, 파라미터를 지원하며 사전에 정의한 기능이 동작하도록 한다.

아래 예제는 클래스의 프로퍼티에 데코레이터를 작성한 것이다.

function fistDecorator(target, name) {
    console.log('fistDecorator');
}

class Person {
    @fistDecorator
    job = 'programmer';
}

const p = new Person();
console.log(p.job);

// fistDecorator
// programmer

또다른 예시로 angular의 app.component.ts 파일에 있는 Component 데코레이터를 살펴보자.

// src/app/app.component.ts

import { Component } from '@angular/core';

@Component({
	selector:'app-root'
	templeteUrl: './app/component.html',
	styleUrls:['./app.component.css']
})
export class AppComponent{
	title: 'my-project';
}
  • component는 앵귤러 사이트에서 view와 해당 view에서 사용될 코드, html, style을 담고 있다.
  • 앵귤러에서 src/index.html을 제외한 모든 view 코드들은 component에 담기게 된다.
    • index.html에서 AppComponent 호출
    • AppComponent 혹은 html 에서 나머지 component들을 호출
  • Component 데코레이터의 설정값
    • Selector: html 코드에서 이 값을 이용해서 해당 component를 사용 (html에서 <selector_name></selector_name> 으로 불러서 사용)
    • TemplateUrl: Component에서 사용하게 될 html 파일의 위치 (하나의 Component에는 하나의 template만 사용 가능)
    • StyleUrl: template에서 사용하게 될 css 파일이 위치 (하나의 Component에서 여러 개의 css 사용 가능, 배열로 작성)

✨ Decorator Factory

function decorator_factory(value: string){
	return function (target) {
		// target과 value 변수를 사용한 동작
	}
}
  • 데코레이터 팩토리를 이용해 테코레이터가 선언에 적용되는 방식을 바꿀 수 있다.
function firstDecorator(param) {
    console.log('factory’);
    return function(target, name) {
        console.log('decorator');
    }
}

class SomeClass {
    @firstDecorator(123)
    prop = ‘a';
}

console.log('인스턴스가 만들어지기 전');
console.log(new SomeClass());

// factory
// decorator
// 인스턴스가 만들어지기 전
  • 데코레이터는 클래스를 인스턴스화하기 위해 클래스를 호출하기 전에 실행된다.
function decoA(param) {
    console.log('decoA factory');
    return function(target, name) {
        console.log('decyA decorator')
    }
}

function decoB(target, name) {
    console.log('decoB decorator');
}

function decoC(param) {
    console.log('decoC factory');
    return function(target, name) {
        console.log('decoC decorator');
    }
}

class SomeClass {
    @decoA(1)
    @decoB
    @decoC(2)
    prop = 1;
}
  • 선언과 동시에 여러 개의 데코레이터를 적용할 수 있다.
  • 실행 순서
    • @expression 표현식이 써진 순서대로 (위에서 아래) 해당 표현식의 팩토리 함수를 실행한다.
    • @expression 을 다 훑고 난 후 데코레이터 함수가 역순으로 (아래에서 위로) 실행된다. 이때 데코레이터 함수란 팩토리 함수에서 반환하는 익명 함수이다. 팩토리 함수가 없다면 일반 함수이다.
profile
홀로서기 기록장

0개의 댓글