타알못 타입스크립트(TypeScript) 0. 타입 기초

TypeScript

타입스크립트를 쓰는 이유

자바스크립트는 타입이 동적으로 결정됩니다. 따라서 자바스크립트를 사용하면 쉽게 개발이 가능합니다. 하지만 값의 타입을 명시하지 않으므로 코드의 가독성이 떨어지며 런타임 도중 각종 오류와 버그가 발생할 수 있습니다.

반면 타입스크립트는 타입이 정적으로 결정됩니다. 코드를 작성하는 도중 실시간으로 에러 검사를 받을 수 있으므로 안정적인 프로그래밍이 가능합니다. 또한 강력한 객체지향 프로그래밍을 지원하며 모듈성, 재사용성, 확장성 및 유지보수성이 자바스크립트에 비해 용이합니다.

타입스크립트로 작성된 코드는 ES3, ES5 및 최신 ES 문법으로 변환됩니다. 오래된 ES 문법으로도 변환하면 호환성은 좋아지지만 코드가 길어지고 성능이 떨어질 수 있습니다.

결론은, 자바스크립트에서 명시하지 않는 값의 타입을 명시함으로써 안정적인 프로그래밍을 가능케하기 위해 타입스크립트를 사용합니다.

타입스크립트 컴파일러 설치

먼저 npm과 node를 설치합니다. 그 다음 npm i -g typescript 명령어로 typescript 컴파일러를 설치한 후 tsc -v 명령어가 잘 실행되는지 확인합니다.

Hello World

main.ts 파일을 다음과 같이 작성합니다.

console.log('Hello World!');

class HelloWorld {
  helloWorld: string;
  constructor() { this.helloWorld = 'Hello World!'; }
}

tsc main.ts 명령어를 실행하면 main.js 파일이 생성되며 node main.js 명령어를 실행하면 콘솔 화면에 Hello World!가 출력됩니다. node는 타입스크립트 문법을 인식하지 못하므로 node main.ts 명령어를 실행하면 오류가 발생합니다.

ts-node

타입스크립트(ts) 파일을 편리하게 실행시키기 위해 ts-node를 사용합니다. 매번 tsc 컴파일러를 사용한 후 node를 사용하는 건 번거롭기 때문입니다. npm i -g ts-node 명령어로 ts-node를 설치한 후 ts-node 파일이름.ts 명령어를 실행하면 자바스크립트 파일로 변환할 필요 없이 타입스크립트 파일을 바로 node로 실행할 수 있습니다.

tsc 컴파일러에 -w(watch) 스위치를 사용하면 파일이 수정될 때마다 자동으로 자바스크립트 파일을 생성해 줍니다. 예를 들어, tsc -w main.ts 명령어를 실행하면 main.ts 파일을 수정할 때마다 새로운 main.js 파일이 자동 생성됩니다.

기본 타입

자바스크립트의 타입은 크게 Primitive(원시)와 Object(객체)로 나뉘며, 세부적으로 아래와 같이 나뉩니다.

  • Primitive(원시) 타입: number, string, boolean, bigint, symbol, null, undefined
  • Object(객체) 타입: function, array

타입스크립트에서는 콜론(:) 기호를 사용하여 타입을 명시합니다. 변수에 어떤 값을 넣을지, 함수가 어떤 값을 반환할지 정하는 것이 타입스크립트의 출발점입니다.

const num: number = -1234;
const str: string = ‘TypeScript’;
const bool: boolean = true;

null과 undefined 타입은 보통 단독으로 지정하지 않고 다른 타입과 함께 지정합니다. 참고로, null은 값이 비어있음을, undefined 타입은 값이 비었는지 안 비었는지 결정되지 않았음을 가리킵니다.

// undefined
let num1: undefined; // undefinded 타입은 보통 단독으로 지정하지 않고
let num2: number | undefined; // 다른 타입과 함께 지정
num2 = undefined;
num2 = 1;

// null
let num3: null; // 보통은 이렇게 쓰지 않고, 아래와 같이 사용
let num4: number | null;

함수(Function) 타입

함수의 매개변수 타입과 반환 타입을 함께 지정합니다.

function add(n1: number, n2: number): number {
  return n1 + n2;
}

Optional parameter는 반드시 전달해하지 않아도 되는 파라미터입니다. 매개변수 뒤에 물음표와 콜론 기호(?)를 붙인 후 타입을 명시합니다.

// optional parameter
function printStr(str1: string, str2?: string) {
  console.log(str1, str2);
}

printStr('Type', 'Script'); // 출력결과: Type Script
printStr('Type'); // 출력결과: Type undefined

Default parameter는 값을 전달받지 않으면 기본 값을 설정받는 파라미터입니다. 타입을 명시한 후 기본 값을 명시합니다.

// Default parameter
function printStr(str: string = 'Type Script') {
  console.log(str);
}

printStr(); // 출력결과: Type Script

Rest parameter는 개수가 유동적인 파라미터입니다. 파라미터를 배열로 정의하면 됩니다.

// Rest parameter
function multiply(...nums: number[]): number {
  return nums.reduce((a, b) => a * b);
}

console.log(multiply(1, 2, 3, 4, 5));

배열(Array) 타입

배열로 선언한 변수 뒤에 배열 타입을 명시합니다.

const nums: Array<number> = [1, 2, 3];
const script: string[] = ['JavaScript', 'TypeScript'];

함수 내에서 배열의 불변성을 유지하고 싶다면 readonly 키워드를 사용합니다.

function printStr(strs: readonly string[]) {
  console.log(strs);
  // strs.push('Error');
}

튜플(Tuple)

튜플은 서로 다른 타입을 담을 수 있는 배열입니다. 튜플은 인덱스를 통해 접근하므로 가독성이 떨어지므로 인터페이스, 타입 앨리어스(Type Alias) 또는 클래스로 대체하여 사용하는 게 좋습니다.

const num: [number, string] = [1, 'One']; // 튜플
console.log(num[0], num[1]); // 결과: 1 One (가독성 Bad)

const [number, spelling] = num; // 구조분해 할당문법
console.log(number, spelling); // 결과: 1 One (가독성 Good)

타입 앨리어스(Type Alias)

타입 앨리어스는 타입을 직접 정의할 수 있는 기능입니다.

// Object 타입 정의
type Num = {
  num: number;
  spelling: string,
}

const num1: Num = {
  num: 1,
  spelling: 'One',
}

console.log(num1.num, num1.spelling);

String Literal Type

String Literal Type은 특정 문자열만 지정할 수 있도록 정의합니다.

type TYPESCRIPT = 'TYPESCRIPT';
const typescript: TYPESCRIPT = 'TYPESCRIPT'; // TYPESCRIPT라는 문자열 값만 지정 가능

유니온 타입(Union Type, OR)

유니온 타입은 여러 타입들 중에서 하나만 정하기 위해 사용합니다. 수직선 기호(|)를 사용하여 정의합니다.

type Direction = 'East' | 'West' | 'South' | 'North'; // 유니온 탕비
function select(direction: Direction) {
  console.log(direction);
}

type InchSize = 13 | 15 | 16;
const macbookSize: InchSize = 15;

Discriminated Union Type은 서로 다른 타입이지만 서로 같은 이름의 속성을 포함하되 그 속성의 값은 다르게 구분짓기 위해 사용합니다.

// Success 타입과 Fail 타입은 result를 공통적으로 가짐 ->  Discriminated Union
type Success = {
  result: 'success';
  data: string;
}

type Fail = {
  result: 'fail';
  reason: string;
}

type Process = Success | Fail;

function printResult(process: Process) {
  if(process.result === 'success')
    process.data = 'SUCCESS';
  else
    process.reason = 'FAIL';
}

인터섹션 타입(Intersection Type ,AND)

인터섹션 타입은 다양한 타입들을 하나로 묶어서 모두 선언하기 위해 사용합니다.

type Chef = {
  name: string;
  work: () => void;
}

type Cook = {
  recipe: string;
  score: number;
}

// 인터섹션 타입
const food: Chef & Cook = {
  name: '김쉐프',
  recipe: '음식 맛은 조미료 맛',
  score: 100,
  work: () => { console.log ('요리 완성!'); }
}

열거형(Enum)

열거형은 이름이 있는 상수들의 집합입니다. 열거형의 요소에 값을 지정하지 않으면 0부터 순차적으로 값이 지정됩니다.

// 열거형: 값을 지정하지 않으면 0부터 시작
enum Season {
  Spring, // 0
  Summer, // 1
  Autumn, // 2
  Winter, // 3
}

// 값을 직접 지정할 수 있음
enum Direction {
  East = 'EAST',
  West = 'WEST',
  South = 'SOUTH',
  North = 'NORTH',
}

enum Smartphone {
  Iphone = 2,
  Galaxy, // 3
  Wing,   // 4
}

console.log(Season.Summer);
const winter: Season = Season.Winter;
console.log(winter);

열거형은 타입이 보장되지 않을 수 있기 때문에 Union Type으로 대체하여 사용하는 것이 좋습니다.

enum Direction1 { East, West, South, North }

// Direction1에 100은 정의되지 않았으나 에러가 발생하지 않음
const south: Direction1 = 100;

// Enum은 Union type으로 대체하여 사용하는 것이 좋음
type Direction2 = 'East' | 'West' | 'South' | 'North';

타입 추론(Type Inference)

타입 추론은 타입스크립트가 알아서 타입을 명시하는 것입니다. 타입을 명시하지 않아도 타입 추론에 의해 타입이 자동으로 정해지지만 웬만하면 타입을 직접 명시해주는 것이 좋습니다.

let str = 'TypeScript'; // 선언함과 동시에 문자열을 지정했으므로 타입이 string으로 자동 지정됨
// str = 1; // 따라서 number 값을 지정하면 에러 발생

타입 단언(Type Assertion)

타입 단언은 타입을 강제로 변경(Casting)하는 것입니다. 어떤 타입인지 정확히 확신할 수 없을 때 타입 단언을 사용하면 에러가 발생할 수 있습니다.

function getStr(): any {
  return 'TypeScript';
}

function getNum(): any {
  return 123;
}

function getStr(): any {
  return 'TypeScript';
}

function getNum(): any {
return 123;
}

const str = getStr();
console.log((str as string).length);
console.log(<string>str.length);

const num = getNum();
console.log(<string>num.length); // undefined

const str = getStr();
console.log((str as string).length);
console.log(<string>str.length);

const num = getNum();
console.log(<string>num.length); // undefined