상세 컨텐츠

본문 제목

TS-타입스크립트 확장 코딩 패턴

노드js·자바스크립트

by 김일국 2023. 11. 2. 13:05

본문

Ps. 아래 타입스크립트의 코딩 패턴을 확인해 보면, 자바(서버프로그램언어)8 버전과 동일한 것을 알 수 있다.

참고로, Java8은 객체지향 언어로 람다식(->애로우함수), 네임스페이스, 제네릭타입, 싱글톤 사용이 가능하다.

즉, 저 처럼 자바(스프링) 개발자에게 진입장벽이 낮은 개발언어가 TypeScript 언어라고 할 수 있다.^^

참고로, 요즘 코딩 트렌드는 Class(클래스) 기반보다는 Function(함수)기반 으로 리액트+TS 프로그램을 구현하는 경우가 많기 때문에 상대적으로 클래스 기술이 코딩 전면에 나타나는 경우가 줄어 들고 있다.

참고로, 자바(오라클-클래스)기반도 코틀린(구글-함수)기반으로 변환 할 수 있는데, 개발자에겐 자바(클래스)기반이 더 코드를 이해하는 방식이 직관적이기 때문에 개발이 용이하다.

------------------------------------------------------------------------------------------------------------

#1. 모듈 코딩 패턴으로 복잡한 기능을 분리할 수 있다.(ES+TS 모두 지원)

------------------------------------------------------------------------------------------------------------

- 스택비즈의 TS 로 실습한다 https://stackblitz.com/fork/typescript (아래)

- index.ts 소스 : import로 복잡한 소스를 정리한 외부 모듈(객체)를 불러온다.(아래)

// Import stylesheets
import './style.css';
//////////////////////////////////////////////////
// TypeScript 모듈을 사용하는 2가지 방법
//////////////////////////////////////////////////
import { on, off } from './Modules'; // 내부 함수명을 개별로 { 함수명, } 객체로 불러올 수 있다. index.ts 파일은 지정하지 않아도 불러온다. 그 외는 파일명을 지정(아래)
import Dom from './Modules/selectors'; // 내부함수가 많으면 대표 모듈명을 변수로 지정해 함수를 불러올 수 있다.
// Dom 모듈을 불러와 사용하는 코드를 작성해봅니다.
console.log(Dom.el('body')); // 오른쪽 화면을 클릭하면 화면이 red 색으로 변한다.(아래)
on(document, 'click', (e) => (Dom.el('body').style.background = 'red'));
// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById('app');
appDiv.innerHTML = `<h1>TypeScript Starter</h1>`;

- Modules/index.ts 소스 : 외부에서 함수명을 사용하려면 export 가 반드시 필요하다. (아래)

export function on(
  el: Element | Document,
  type: string,
  handler: (e: Event) => void,
  is_capture: boolean = false
): void {
  el.addEventListener(type, handler, is_capture);
}

export function off(
  el: Element | Document,
  type: string,
  handler: (e: Event) => void,
  is_capture: boolean = false
): void {
  el.removeEventListener(type, handler, is_capture);
}

- Modules/selectors.ts 소스 : default로 내보내진 모듈은 import 구문에서  대표 모듈이름(변수명)으로 사용가능. (아래)

const document = window.document;

function el(selector: string, context: Element | Document = document): Element {
  return context.querySelector(selector);
}

function els(
  selector: string,
  context: Element | Document = document
): NodeList {
  return context.querySelectorAll(selector);
}

export default { el, els };

- 위 코드를 실행한 결과 : 스택비즈의 TS 화면(아래)

Ps. 참고로 네임스페이스로 복잡한 기능을 이름으로 묶어서 사용할 수 있다.(TS-타입스크립트 전용: 아래 샘플소스)

// 네임스페이스 Dom 정의
namespace Dom {
  // 외부에서 접근 불가
  const document = window.document;
  // 외부에서 접근 가능하도록 export
  export function el(selector:string, context:HTMLElement|Document = document) {
    return context.querySelector(selector);
  }
  // 외부에서 접근 가능하도록 export
  export function els(selector:string, context:HTMLElement|Document = document) {
    return context.querySelectorAll(selector);
  }
}
// [오류] 'el' 이름에 접근 불가(아래)
//console.log(el('body')); // 오류
// 네임스페이스 Dom을 통해서만 정의된 el()에 접근 사용 가능합니다.
console.log(Dom.el('body')); // <body>

 

------------------------------------------------------------------------------------------------------------

#2. 제네릭 코딩 패턴사용: new 키워드로 객체를 생성할 때 사용자가 타입을 지정해 사용할 수 있다. 사용 방법은 클래스 이름 뒤에 <T>를 붙인다. T는 관용적인 식별자로 다른 이니셜을 사용해도 된다.(TS 전용)

------------------------------------------------------------------------------------------------------------

- 스택비즈의 TS 로 실습한다 https://stackblitz.com/fork/typescript (아래)

- 제네릭이란?
제네릭(Generics)은 클래스 또는 함수에서 사용할 타입(Type)을, 그 클래스나 함수를 사용할 때 즉, new 키워드로 객체를 생성할 때 결정하는 프로그래밍 기법을 말한다. 참고로, 서버프로그램언어인 자바에서도 Java8 부터 지원하는 기능이다.
- 제네릭이 필요한 이유 : 아래 제네릭을 사용하지 않은 코딩과 사용한 코딩을 비교해 보면서 확인해 본다.
-
제네릭을 사용하지 않는 코딩 : 객체가 어떤 데이터를 사용하는지 객체생성 단계에서 확실히 알수없다.(아래[핵심]부분)

// Import stylesheets
import './style.css';
//import Model from './classes/Model'; //코드가 길면 외부 모듈로 분리할 수 있다.
// 불특정 유형의 데이터 처리 가능한 모델 클래스를 정의.
class Model {
  private _data: any[] = [];
  //생성자에 불특정 데이터 받기
  constructor(data: any[]=[]) {
    this._data = data;
  }
  get data(): any {
    return this._data;
  }
  // 함수에 불특정 데이터 받기
  add(item: any): void {
    this._data.push(item);
  }
  remove(index: number): void {
    this._data.splice(index, 1);
  }
  item(index: number): any {
    return this._data[index];
  }
  clear(): void {
    this._data = [];
  }
}
// 특정 데이터만 처리 가능한 모델 클래스를 정의.
class ModelList extends Model {
  //생성자에 특정 데이터 받기 : 배열 object형
  constructor(data: object[] = []) {
    super(data);
  }
  //함수에 특정 데이터 받기 : object형
  add(item: object): void {
    super.add(item);
  }
}
// ------------------------------------------------
// Model 클래스는 어떤 데이터 유형도 처리 가능.
let member_1: Model = new Model();
let member_2: Model = new Model();
// 어떤 데이터 타입도 추가 가능합니다.
member_1.add(1);
member_1.add('홍길동');
member_1.add(false);
member_2.add(2);
member_2.add('황진이');
member_2.add(true);
console.log(member_1.data);
console.log(member_2.data);
let member_list: ModelList = new ModelList();
//member_list.add(4); // object객체만 데이터로 받을 수 있다.
//member_list.add(false);// object객체만 데이터로 받을 수 있다.
member_list.add(member_1);
member_list.add(member_2);
/** 또는 아래 처럼 사용가능하다.
// [핵심] 아래 객체가 어떤 데이터를 사용하는지 객체생성 단계에서 확실히 알수없다.(아래)
let member_list: Model = new Model();
member_list.add(member_1);
member_list.add(member_2);
*/
console.log(member_list.data);
// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById('app');
appDiv.innerHTML = `<h1>TypeScript Starter</h1>`;

- 제네릭을 사용한 코딩 : 객체가 어떤 데이터를 사용하는지 new로 객체생성 단계에서 확실히 알수있다.(아래[핵심]부분)

// Import stylesheets
import './style.css';
//import Model from './classes/Model'; //코드가 길면 외부 모듈로 분리할 수
// new 객체로 만들 때 불특정 유형의 데이터 처리 가능한 모델 클래스를 정의.
class Model<T> {
  private _data: T[] = [];
  constructor(data: T[] = []) {
    this._data = data;
  }
  get data(): T[] {
    return this._data;
  }
  add(item: T): void {
    this._data.push(item);
  }
  remove(index: number): void {
    this._data.splice(index, 1);
  }
  item(index: number): T {
    return this._data[index];
  }
  clear(): void {
    this._data = [];
  }
}
// ------------------------------------------------
// Model 클래스는 new 키워드로 생성시 데이터 유형을 처리하는 것이 가능.(아래)
let member_1: Model<any> = new Model<any>(); // any로 모든 데이터 형을 입력할 수 있다.
let member_2: Model<string> = new Model<string>(); // string 으로 문자 데이터만 입력 할 수 있다.
member_1.add(1);
member_1.add('홍길동');
member_1.add(false);
member_2.add(<any>2); // string 형구조에 숫자를 입력할 때 형변환을 시킬수 있다.
member_2.add('황진이');
member_2.add(true as any); // string 형구조에 숫자를 입력할 때 형변환을 시킬수 있다.
console.log(member_1.data);
console.log(member_2.data);
// [핵심] 아래 객체가 어떤 데이터를 사용하는지 객체생성 단계에서 확실히 알수있다.(아래)
let member_list: Model<object> = new Model<object>();
member_list.add(member_1);
member_list.add(member_2);
console.log(member_list.data);
// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById('app');
appDiv.innerHTML = `<h1>TypeScript Starter</h1>`;

- 위 코드를 실행한 결과 : 스택비즈의 TS 화면(아래)

- 위 실행 결과에서 부자연 스러운 any 사용(노란색 밑줄부분)을 해결할 멀티타입을 사용한 코드 : (아래 [수정]부분)

// ------------------------------------------------ 이전코드 동일 생략...
// Model 클래스는 new 키워드로 생성시 데이터 유형을 처리하는 것이 가능.(아래)
let member_1: Model<any> = new Model<any>(); // any로 모든 데이터 형을 입력할 수 있다.
// [수정]3가지 타입의 데이터만 입력 할 수 있다.(아래 멀티타입 코드)
let member_2: Model<string|number|boolean> = new Model<string|number|boolean>(); 
member_1.add(1);
member_1.add('홍길동');
member_1.add(false);
member_2.add(2); // [수정]멀티타입에 숫자를 입력 수 있다.
member_2.add('황진이');
member_2.add(true); // [수정]멀티타입에 불린을 입력할 수 있다.
console.log(member_1.data);
console.log(member_2.data);
// 이하코드 생략...

- 제네릭T를 사용하는 장점: 실제 현업에서는 아래와 같은 2차원 구조 자료(json데이터)를 주로 사용한다.(아래)

// ------------------------ 이전 코드 생략: 상단의 Model 클래스는 동일하다. 제네릭T 사용의 장점이다.
// [수정] 아래와 같은 2차원 구조 자료를 현업에서 주로 사용한다.(아래)
let member_1: Model<{[key: string]: any}> = new Model<{[key: string]: any}>(); // string 으로 문자 데이터만 입력 할 수 있다.
let member_2: Model<{[key: string]: any}> = new Model<{[key: string]: any}>(); // string 으로 문자 데이터만 입력 할 수 있다.
member_1.add({id:1, name:'홍길동', login_checked:false}); // string 형구조에 숫자를 입력할 때 형변환을 시킬수 있다.
member_2.add({id:2, name:'황진이', login_checked:true}); // string 형구조에 숫자를 입력할 때 형변환을 시킬수 있다.
console.log(member_1.data);
console.log(member_2.data);
// [핵심]이 객체가 어떤 데이터를 사용하는지 객체생성 단계에서 확실히 알수있다.(아래)
let member_list: Model<object> = new Model<object>();
member_list.add(member_1);
member_list.add(member_2);
// [추가] 아래처럼 직접 입력방식을 연업에서 주로 사용한다.(아래)
member_list.add({id:3, name:'아무개', login_checked:true, level:10});
console.log(member_list.data);

- 위 2차원 데이터 코드를 실행한 결과 : 스택비즈의 TS 화면(아래)

제네랙T타입을 사용하면, 상단의 Model클래스는 변화없이 하단 new 실행 부분에서 데이터를 사용할 수 있다.(위)

------------------------------------------------------------------------------------------------------------

#3. 싱글톤(Singleton) 코딩 패턴 사용: (ES6+TS 모두지원)

- 클래스의 객체(인스턴스-Instance)를 한개만 생성되게 하는 코딩패턴으로 개발자가 실수로 객체를 2개 이상 생성하는 것을 막아준다. 즉, 자원(메모리)를 절약하여 활용성을 높이는 장점이 있다

------------------------------------------------------------------------------------------------------------

- 스택비즈의 TS 로 실습한다 https://stackblitz.com/fork/typescript (아래)

private 접근 제어자를 사용해 constructor() 앞에 붙이면 new 키워드를 통해 인스턴스를 생성하지 못하도록 제한할 수 있다. 대신 공개된 스태틱 메서드 getInstance()를 통해 오직 한 번만 인스턴스를 생성할 수 있게 된다. 이를 싱글턴 패턴이라 부르고, 아래 예, 데이터(DataBase)를 커넥션해서 불러오는 코드 처럼 1번만 생성해도 되는 Service 객체를 실수로 2개 생성하는 것을 막아준다. 즉, 자원(메모리) 활용성을 높이는 장점이 있다.(아래)

let service = new Service(`https://jsonplaceholder.typicode.com/todos`);
service.request(data => console.log(data));

... 기타 등등 코딩 후 아래처럼 new 키워드로 새 서비스 객체를 생성하면 메모리 낭비가 발생한다.

let service_json = new Service(`https://jsonplaceholder.typicode.com/todos`);
service_json.request(data => console.log(data));

- 위 싱클톤 코딩의 실제 예 : 원격 Api서비스에 접속하여 데이터를 가져오는 코드(아래)

// Import stylesheets
import "./style.css";
//////////////////////////////////////////////////
// TypeScript 싱글톤 패턴으로 구현한 서비스 객체
//////////////////////////////////////////////////
class Service {
  private static instance:Service; //인스턴스(객체) Service타입 변수를 static 메모리로 지정
  public url:string;
  private constructor(url:string) { //private이기 때문에 new Servie('')로 실행(접근)할 수 없다.
    this.url = url;
  }
  public request(cb:(response)=>void):void {
    fetch(this.url)
      .then(res => res.json()) // fetch함수의 응답 내용을 json 형태로 반환 한 후 아래 진행
      .then(cb); //cb(callBack)함수의 response변수에 위 json 데이터를 전달하고 void 함수를 실행한다.
  }
  public static getInstance(url:string):Service {
    if (!Service.instance) {
      Service.instance = new Service(url);
    }
    return Service.instance;
  }

}
let service = Service.getInstance(`https://jsonplaceholder.typicode.com/todos`);
/** Service클래스의 request()함수의 cb콜백 변수의 response가 data로 전달,
 void 가 console.log() 함수로 매칭된다.(아래)
*/
service.request((data) => console.log(data));
// Write TypeScript code!
const appDiv: HTMLElement = document.getElementById('app');
appDiv.innerHTML = `<h1>TypeScript Starter</h1>`;

- 위 싱글톤 클래스를 객체로 사용하는 코드를 실행한 결과 : 스택비즈의 TS 화면(아래)

- Ps. 기술참조 : https://yamoo9.gitbook.io/typescript/

관련글 더보기

댓글 영역