성장, 그리고 노력

부족하더라도 어제보다 더 잘해지자. 노력은 절대 배신하지 않는다.

Typescript

[TypeScript] 타입스크립트 기본기 다지기

제이콥(JACOB) 2019. 12. 8. 22:13

타입스크립트(TypeScript) - 정적 타입 언어

자바스크립트를 사용할때 안정성 있는 프로그래밍을 위한 방법입니다.
조금만 어렵게 말해보자면, 동적 타입 언어인 자바스크립트의 변수 타입은 런타임시에 결정되기 때문에 타입 오류 또한 런타임시에 발견됩니다.
TMI이긴 하지만, 타입스크립트는 델파이의 창시자인 앤더스 헤일스버그(Anders Hejlsberg)라는 분이 만들었습니다. 그래서인지 타입스크립트를 하시다보면 C#과 비슷한데...라는 생각이 드실수도 있어요. 당연하겠죠? 워낙 유명하시니깐요..

[javascript]

  let sampleNumber = 9208;
  sampleNumber = "구이공팔";  

(자바스크립트에서는 문제 없이 실행됨)
그러다보니 다른 정적 타입 언어(Java, C++)에 비해 배우기는 쉽지만 타입 오류가 늦게 발견되고 이는 예기치 못한 또 다른 오류로 이어질 수 있습니다. 그래서 npm에 많은 유명 자바스크립트 라이브러리들은 타입스크립트(TypeScript)를 사용하고 있으며, 많이 사용되는 IDE인 vscode에서는 기본적으로 타입 스크립트 파일의 타입 검사를 자동으로 실행합니다.

[typescript]

  let sampleNumber: number = 9208;
  sampleNumber = "구이공팔"; // 넌 오류얌! 

물론 숫자도 되고 문자도 되는 상황이라면 아래와 같이 코드를 작성해도 됩니다.


[typescript]

  let sampleNumber: number | string = 9208;
  sampleNumber = "구이공팔"; // 둘다 좋아! 

  타입스크립트는 마이크로소프트(Microsoft) 에서 개발하고 있기 때문에 자바스크립트의 새로운 표준이 나와도 반영이 빠르게 됩니다(갑자기 신뢰성 upup!).
  본 글은 입문자를 위한 것이므로 최대한 쉽고 자세하게 적었습니다. 물론 이게 모든 내용을 담고 있다는 점을 의미하지 않습니다. 더 자세한 내용은 TypeScript Document를 확인하시길 바랍니다.
그럼 시작합니다.

타입스크립트 시작하기(npm)

npm init --yes

npm i -g typescript

npm init만 입력 후, 정보를 넣고 싶으면 넣어도 됩니다. 다만 어차피 입력 안하고 엔터(enter)만 치실 분이라면(저처럼...) --yes 옵션 주면 끝납니다.

 

기본 설정(Recommand)

tsconfig.js 파일을 생성하고 아래 코드 작성(또는 tsc --init)

{
  "compilerOptions": {
    "module": "commonjs", // node.js를 평범하게 사용하고 다양한건 import, export 할 수 있게 해줌,
    "target": "ES2015", // 어떤 버전의 자바스크립트로 컴파일 되고 싶은지를 기록
    "sourceMap": true // 번들 파일 내의 코드를 원래 소스 파일로 매핑 시킬지 여부, 배포시에는 false
  },
  "include": [
    // 컴파일 과정에 포함할 파일을 배열로 저장
    "index.ts"
  ],
  "exclude": ["node_modules"] // 제외할 파일 
}

cf) 만약 타입스크립트의 any타입을 강제하고 싶다면 compilerOptions"noImplicitAny": true,를 추가합니다. 전 안하고 진행하겠습니다. 이 경우 컴파일시에 컴파일러가 알아서 any를 넣어줍니다. 그리고 any는 모든 타입의 최상위 타입입니다.

타입스크립트 컴파일러 명령어(tsc)

만약 tsc를 통해 index.ts를 컴파일 했다면, index.js,index.js.map 파일이 생성됨

> tsc
> tsc index.ts // 특정 파일

cf) tsc는 타입스크립트 컴파일러이자 트랜스파일러(transpiler) 입니다. tsc 커맨드를 통해 최신 문법을 ES5로 변경 및 컴파일해줍니다.

package.json confing

scripts를 추가해서 컴파일을 좀더 편리하게 하기

  "scripts": {
    "start": "node index.js",
    "prestart": "tsc"
  },

==> npm start -> prestart -> tsc -> index.js 생성됨 -> node index.js
이렇게 해주는 이유는 Node.js는 타입스크립트를 이해하지 못하기 때문에 일반적인 자바스크립트(javascript) 코드로 컴파일하는 작업이 필요함.
(만약 tsc--target es6이라고 컴파일 옵션을 준다면 해당 버전으로 컴파일할 수 있습니다.)


예제(index.ts)

  const helloMaster = (name, age, lang) => {
    console.log(`Hello ${name}, you are ${age}, Wellcome ${lang}!`);
  };

  helloMaster("masterJung", 28, "typescript" );

  export {}; // 없으면 에러. 버그같은 거 일단 넘어가기.

TS 문법1 - Argument

인자(argument)의 타입을 지정할 때에는 인자 뒤에 콜론(colon, :)을 입력하고 그 뒤에 타입을 입력한다.

  const helloMaster = (name: string, age: number, lang: string) => {
    console.log(`Hello ${name}, you are ${age}, Wellcome ${lang}!`);
  };

  helloMaster("masterJung", 28, "typescript");

  export {};

TS 문법2 - Function

인자 괄호가 끝난 후 콜론을 입력하고 그 뒤에 return 타입을 입력한다. 만약 리턴타입이 없다면 void를 입력한다.

  const helloMaster = (name: string, age: number, lang: string): void => {
    console.log(`Hello ${name}, you are ${age}, Wellcome ${lang}!`);
  };

  helloMaster("masterJung", 28, "typescript");

  export {};
  Hello masterJung, you are 28, Wellcome typescript!

TS 문법3 - Argument (Option type)

만약 인자가 있어도 되고, 없어도 된다면 인자 뒤에 물음표(?) 를 입력 후, 콜론과 타입을 순서대로 정의한다.

  const helloMaster = (name: string, age: number, lang?: string): void => {
    console.log(`Hello ${name}, you are ${age}, Wellcome ${lang}!`);
  };

  helloMaster("masterJung", 28); // 오류가 나지 않음

  export {};

cf) 만약 인자를 입력하지 않았다면 undefined로 정의된다.

  Hello masterJung, you are 28, Wellcome undefined!

cf) option type의 경우 당연히, 초깃값 설정을 함께 할 수 없습니다.

쉬어가기(tsc-watch)

문법을 3개나 익혔으니 잠시 쉬었다 가겠습니다. (머리 폭발...)
근데 혼자 테스트 하다보면 짜증이 슬슬 나지 않나요? 테스트를 할때마다 npm start를 계속 반복해서 입력하자나요. 이때 그리운 것이 바로 데몬 같은 패키지 입니다(node.js의 nodemon 같이 코드가 변경될때마다 서버를 자동 재시작하면 좋지 않을까요?). 그래서 필요한 것이 tsc-watch 입니다.

npm i tsc-watch --save-dev
개발 환경에서만 사용하므로 -dev옵션도 붙여줍니다.

!여기서 중요한점은: 버그 같은데, TS를 전역으로 설치했다면 tsc-watch 실행시 경로를 못찾습니다. 그냥 맘편히 한번더 설치해 주세요.
npm i typescript
그리고 tsconfig.json과 package.json파일을 각각 수정해줍니다.

1. [tsconfig.json]

기존에 작성한 .ts파일은 모두 src폴더를 만든 후 이동해 주세요. 이전에 컴파일 작업에서 생성된 ~.js, ~.js.map은 이제 필요 없으니 삭제!

   "compilerOptions": {
    ...,
    "outDir": "dist" //ts는 모두 scr 폴더로 넣고, 컴파일된 파일은 모두 dist 폴더
  },
  ...,
  "include": ["src/**/*"]
  ...

2. [package.json]

root 디렉토리에 dist 폴더를 만들고 아래와 같이 수정합니다.

    ...,
    "scripts": {
    "start": "tsc-watch --onSuccess \"node ./dist/index.js\" "
  },
  ...

3. 실행(npm start)

이전과 동일하게 실행하면 tsc-watch가 작동하게 되고, ts파일에 변경이 있을시 자동으로 반영됩니다.

TS 문법4 - Object

java와 같은 언어를 경험해보았다면, 아마도 Object 타입이 익숙합니다. 물론 더 편리하기도 하고 때로는 코드의 양을 많이 줄여주기도 하고요. TS에서 object를 사용하기 위한 가장 간단한 방법은 인터페이스(interface) 를 사용하는 겁니다. 주의할 점은 자바스크립트에서는 안되고 타입스크립트에서만 가능한 Object이니, 헷갈리지 마세요.

1. 인터페이스를 정의합니다.

interface Info {
  name: string;
  age: number;
  lang: string;
}

2. 함수의 인자값을 설정하되, 타입을 인터페이스 타입으로 지정합니다.

여기서 인터페이스 안에 있는 값은 인터페이스명.속성명 으로 합니다.

const helloMaster = (person: Info): void => {
  console.log(
    `Hello ${person.name}, you are ${person.age}, Wellcome ${person.lang}!`
  );
};

3. Object를 정의합니다.

const person1 = {
  name: "masterJung",
  age: 28,
  lang: "typescript"
};

4. 인자값으로 Object를 넘겨줍니다.

  helloMaster(person1); // 오류가 나지 않음

[전체 코드]

interface Info {
  name: string;
  age: number;
  lang: string;
}

const person1 = {
  name: "masterJung",
  age: 28,
  lang: "typescript"
};

const helloMaster = (person: Info): void => {
  console.log(
    `Hello ${person.name}, you are ${person.age}, Wellcome ${person.lang}!`
  );
};

helloMaster(person1);

export {};

  여기서 한 가지 특이점은 interface는 컴파일되지 않습니다. 실제로 dist 파일에서 확인해 보셔도 interface는 컴파일되지 않는 점을 확인하실 수 있을거에요. 그렇다면 내가 반드시 인터페이스를 컴파일하고 싶다면 어떻게 해야될까요?
이때 사용되는 개념은 Class 입니다.

TS 문법5 - class

  자바와 같은 정적 타입 언어를 사용해보셨다면, 이 파트는 매우 쉽게 넘어갈 수 있습니다. 만약 이해가 되지 않는다면, 공부를 하다보면 이해가 되는 부분이니 너무 스트레스 받지 말고 가볍게 보시길 바랍니다.

1. 기존에 작성한 인터페이스와 person1은 삭제합니다.

2. 아래에 클래스 코드와 생성자를 작성합니다.

class Info {
  public name: string; 
  public age: number;
  public lang: string;

  constructor(name: string, age: number, lang: string) {
    this.name = name;
    this.age = age;
    this.lang = lang;
  }
}

  클래스 내에 있는 멤버 변수에는 public이 있다는 것은 당연히 private도 있습니다.
먼저 public 은 프로그램 전체에서 자유롭게 접근할 수 있습니다. 물론 public이라고 명시적으로 작성하지 않아도 됩니다(C#과 다른점, 또한 default가 아닌 public).
private 은 private을 선언한 클래스 외에서는 접근할 수 없는 멤버변수입니다. 때문에 위의 예제에서 public을 private으로 바꾼다면 클래스 밖에서 멤버 변수를 호출해서 사용했으므로 오류가 발생합니다.

  src/index.ts(17,69): error TS2341: Property 'lang' is private and only accessible within class 'Info'.

protect도 있지만, 사용빈도가 낮으므로 생략합니다.

TS 문법5 - getter, setter

  private를 본 순간 자바를 공부했다면 본능적으로 일명 '겟터, 셋터'를 찾게됩니다. 특히 setter의 경우 멤버 변수를 숨기고, 값 할당시에 제약 조건 등을 적용할 수 있으니, 매력적이잖아요. 위 예제를 일단 조금 간소화 시키겠습니다.

getter, setter 적용전 코드 간소화

class Info {
  public name: string;
}

const personInfo1 = new Info();

const helloMaster = (person: Info): void => {
  console.log(`Hello ${person.name}!`);
};

helloMaster(personInfo1);

export {};
> Hello undefined! // 이렇게 나온다면 정상!

getter, setter 적용!

  멤버변수를 private으로 변경 후 작성합니다(private 변수로 변경하면서 멤버변수와 getter, setter 이름이 겹치면 안되기 때문에 멤버 변수앞에 _ (언더바)를 붙였습니다(정해진게 아니니 편하신대로 명명하시면 됩니다).

아래 예제에서는 DB설계상 name이 10글자가 넘으면 안된다는 가정하에 만들어보겠습니다.

class Info {
  private _name: string;

  get name(): string {
    return this._name;
  }

  set name(inputName: string) {
    if (inputName && inputName.length > 10) {
      throw new Error(
        `이름은 최대 10글자가 넘으면 안되요. 현재 글자 수: ${inputName.length}`
      );
    }
    this._name = inputName;
  }
}

const personInfo1 = new Info();
personInfo1.name = "masterJung";

const helloMaster = (person: Info): void => {
  console.log(`Hello ${person.name}!`);
};

helloMaster(personInfo1); 

export {};
> Hello masterJung!

만약 11글자를 입력했다면?

> Error: 이름은 최대 10글자가 넘으면 안되요. 현재 글자 수: 11
    at Info.set name [as name] (D:\index.js:9:19)
    ...
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)
반응형