/ #타입스크립트

타입스크립트 문법(TypeScript Syntax)

기본 타입

자바스크립트는 다음과 같이 7가지 내장 타입을 제공합니다.

  • Number
  • String
  • Boolean
  • Symbol
  • Null
  • Undefined
  • Object

타입스크립트에는 자바스크립트의 내장 타입에 해당하는 원시 타입이 있습니다.

  • number
  • string
  • boolean
  • symbol
  • null
  • undefined
  • object

타입스크립트에만 존재하는 원시 타입도 있습니다.

  • unknown
  • never
  • void
  • any

그 외에도 다음과 같은 참조 타입이 있습니다.

  • Array
  • Tuple
  • Enum

원시 타입(Primitive Type)

불리언(Boolean)

boolean은 가장 기본적인 데이터 타입으로, 참/거짓(true/false)값을 나타냅니다.

let isDone: boolean = false;

숫자(Number)

자바스크립트처럼 타입스크립트의 모든 숫자는 부동소수점을 사용합니다. 16진수, 10진수 literal과 2진수, 8진수 literal도 지원합니다.

let decimal: number = 10;
let hex: number = 0xff0a;
let binary: number = 0b1010;
let octet: number = 0o777;

문자열(String)

자바스크립트와 마찬가지로 큰따옴표(") 또는 작은따옴표(')를 통해 문자열을 나타냅니다.

let color: string = "red";
let name: string = 'rubisco';

백틱(`)으로 문자열을 감싸서 템플릿 문자열을 만들 수도 있습니다.

let name: string = "rubisco";
let introduce: string = `안녕하세요? ${ name } 입니다.`; // "안녕하세요? " + name + " 입니다." 와 동일

Null / Undefined

null과 undefined 타입은 각각 nullundefined라는 하나의 값만을 가집니다. null은 변수를 선언했지만 빈값을 할당한 상태를 의미하며, undefined는 변수를 선언했지만 값이 없는 상태 즉, 타입이 없는 상태를 의미합니다. typeof 메소드를 통해 자료형을 확인해보면 null은 object, undefined는 undefined 타입인 것을 확인할 수 있습니다.

null과 undefined는 기본적으로 모든 타입의 하위타입이므로 아무런 설정이 없다면 아래와 같은 할당이 허용됩니다.

let name: string = null;

하지만 이러한 동작은 버그를 양산하기 쉽습니다. 엄격한 타입 체크를 위해 타입스크립트 2.0부터 옵션으로 --strictNullChecks 플래그가 추가되었습니다. 옵션에 이 플래그를 추가하면 null과 undefined 값을 다른 타입에 할당하는 것을 방지할 수 있습니다.

Any / Unknown

any 타입은 모든 데이터 타입을 허용할 때 사용합니다. 데이터 타입을 미리 알 수 없는 경우 사용할 수 있지만 불가피한 경우를 제외하고는 사용하지 않는것이 권장됩니다. 보통 자바스크립트 문법을 점진적으로 타입스크립트 문법으로 바꿀때 사용합니다.

let letter: any = "문자";
letter = 20; // string 타입이 들어있지만 number 타입이 들어갈 수 있습니다.

unknown 타입은 타입스크립트 3.0부터 도입되었습니다. any 타입과 동일하게 모든 타입의 값을 허용하지만 엄격한 타입 체크를 요구합니다.

let num: any = 10;
console.log(num.length); // undefined 출력

let unknownValue: unknown = 10;
console.log(unknownValue.length); // 에러 발생 : Object is of type 'unknown'.ts(2571)

num 변수와 unknwonValue 변수에는 숫자가 할당되어 length 메소드를 가지고 있지 않습니다. any타입인 num의 경우 length 메소드를 호출하면 undefined 값을 출력합니다. 반면에 unknown 타입인 unknownValue는 오류를 출력합니다.

unknown 타입의 경우 다음과 같은 엄격한 타입체크가 필요합니다.

let unknownValue: unknown = 10;

if(typeof unknownValue === "string") {
    console.log(unknownValue.length);
} else {
    console.log(undefined);
}

Void / Never

void 타입은 null과 undefined만 값으로 가질 수 있는 타입입니다. 함수에서는 리턴값이 없을때 사용합니다.

function msg_logger(msg: any): void {
    console.log(msg);
}

never 타입은 아무런 값을 가지지 않는 타입입니다. null, undefined를 포함한 모든 타입의 최하위 타입에 속하기 때문에 never을 제외한 어떤 값도 할당될 수 없습니다. 무한 루프를 돌거나 에러를 출력하는 함수의 경우 리턴값 자체가 없기때문에 never 타입을 가집니다.

function infiniteLoop(): never {
    while (true) {} // 무한 루프
}

function error(message: string): never {
    throw new Error(message); // 에러 출력
}

참조 타입(Reference Type)

객체(Object)

자바스크립트는 프로토타입 객체 기반 언어로, 원시 자료형을 제외한 모든 데이터가 객체(Object)로 구성됩니다. object 타입은 중괄호({})를 사용하여 선언할 수 있습니다.

let user: {name: string, age: number} = {name: '홍길동', age: 19};

위에 코드에 보는 바와 같이 콜론(:)의 좌변에는 속성의 키워드값이, 우변에는 속성 타입이 들어갑니다. 각 속성의 구분은 위에 코드처럼 콤마(,)로 구분하며, 세미콜론(;)을 통해서도 구분가능합니다.

let user: {
    name: string; 
    age: number;
} = {name: '홍길동', age: 19};

속성명 뒤에 물음표(?)를 붙이면 해당 속성을 선택 속성으로 선언할 수 있습니다.

let user: {
    name: string; 
    age: number;
    comment?: string;
} = {name: '홍길동', age: 19};

위 코드에서 comment의 값은 할당되지 않았지만 선택속성이므로 오류없이 객체 값이 할당될 수 있습니다.

속성명 앞에 readonly 키워드를 붙이면 값의 재할당을 막을 수 있습니다. 변수에서 const 키워드를 통한 변수 할당과 동일합니다.

let user: {
    readonly name: string; 
    age: number;
} = {name: '홍길동', age: 19};

user.name = '고길동'; // error TS2540: Cannot assign to 'name' because it is a constant or a read-only property.

배열(Array)

배열의 경우 2가지 방법으로 타입을 지정할 수 있습니다.

첫번째 방법은 타입 뒤에 []을 붙이는 것입니다.

let colors: string[] = ['red','orange','yellow','green'];

두번째 방법은 제네릭(generic) 배열 타입을 사용하는 것입니다.

let colors: Array<string> = ['red','orange','yellow','green'];

튜플(Tuple)

튜플은 배열과 유사하며, 요소(element)의 수가 고정된 배열을 나타낼 수 있습니다.

let member: [number, string] = [1, 'rubisco'];

열거(Enum)

열거형은 자바의 열거형과 마찬가지로 숫자 또는 문자열 값의 집합에 이름을 부여할 수 있습니다.

enum Week { SUN, MON, TUE, WED, THU, FRI, SAT }

let today: Week = Week.WED;

인덱스 번호로도 접근가능합니다.

enum Week { SUN, MON, TUE, WED, THU, FRI, SAT }

let tomarrow: Week = Week[4]; // Week.THU와 동일

자바처럼 초기 인덱스 번호를 지정해 줄 수도 있습니다.

enum Week { SUN = 1, MON, TUE, WED, THU, FRI, SAT }

let yesterday: Week = Week[3]; // Week.TUE와 동일

또한 자바와 달리 문자열 값을 지정해 줄 수 있습니다.

enum Color {
    RED = "red",
    BLUE = "blue",
    GREEN = "green"
}

let color: Color = Color.RED; // 문자열 "red"와 동일

타입 추론과 타입 단언

타입 추론(Type Inference)

타입 추론(Type Inference)이란 타입스크립트가 코드를 해석해나가는 동작을 의미합니다. 아래와 같이 타입을 선언하지 않고 변수에 값을 할당하면 타입스크립트에서는 str 변수를 string 타입으로 추론합니다.

let str = "spot" // string 타입 값이 할당되었으므로 string 타입으로 추론

그러므로 str 변수에 문자열이 아닌 숫자를 재할당한다면 오류가 발생합니다.

str = 1 // Type 'number' is not assignable to type 'string'.

constreadonly 키워드가 있는 경우 타입스크립트는 구체적인 literal 타입을 추론합니다.

const x = 1 // 1로 추론

타입 단언(type assertion)

타입스크립트 컴파일러는 타입 표기, 타입 추론 등을 이용해 값의 타입을 판단합니다. 하지만 때로는 컴파일러가 가진 정보를 무시하고 개발자가 원하는 임의의 타입을 값에 할당하고 싶을 수 있는데, 이때 필요한 것이 타입 단언(type assertion)입니다.

let div = document.querySelector('div');
div.innerText = "rubisco"; // Object is possibly 'null'.ts(2531)

위의 코드의 경우 타입스크립트는 div가 null일 경우까지 추론하게 됩니다. div가 null일 경우 innerText 메소드가 존재하지 않게 되므로 오류를 출력하게 됩니다. 이럴때 변수 뒤에 as 키워드를 붙여 타입 단언을 해줄 수 있습니다. 자바에서 명시적 형변환(Casting)과 같은데, 형변환은 런타임에서 발생하는 동작이고 타입 단언은 컴파일시 발생하는 동작이라는 차이가 있습니다.

let div = document.querySelector('div');
(div as HTMLDivElement).innerText = "rubisco";

변수 뒤에 느낌표(!)를 붙여서 값이 무조건 존재한다는 확정적 할당 단언(definitive assignment assertion)을 함으로써 이를 해결할 수도 있습니다.

let div = document.querySelector('div');
div!.innerText = "rubisco";

제네릭(Generic)

제네릭은 자바 또는 C# 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징입니다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용됩니다. 함수나 클래스를 사용하는 시점에 타입을 선언함으로써 타입을 파라미터처럼 사용할 수 있습니다.

제네릭 타입은 함수 이름 뒤에 괄호(<>)로 표기하며 괄호안에 타입명을 적습니다. 관습적으로 TType, EElement, KKey, VValue, NNumber를 뜻합니다.

아래와 같이 코드를 작성해 봅시다.

function toArray(a: number | string, b: number | string): (number | string)[] {
    return [a, b];
}

let o1 = toArray(1, 2); // 배열 [1, 2]가 대입
let o2 = toArray("a", "b"); // 배열 ["a", "b"]가 대입

타입 선언에서 | 기호는 OR를 의미합니다. 다시 말해서 toArray 함수의 파라미터는 숫자형이나 문자형의 데이터를 받을 수 있습니다. 이를 제네릭을 통해 선언하면 다음과 같이 작성할 수 있습니다.

function toArray<T>(a: T, b: T): T[] {
    return [a, b];
}

let o1 = toArray<number>(1, 2); // 배열 [1, 2]가 대입
let o2 = toArray<string>("a", "b"); // 배열 ["a", "b"]가 대입

타입 추론을 통해 아래와 같이 타입 변수를 생략해도 됩니다.

function toArray<T>(a: T, b: T): T[] {
    return [a, b];
}

let o1 = toArray(1, 2); // 배열 [1, 2]가 대입
let o2 = toArray("a", "b"); // 배열 ["a", "b"]가 대입

타입 변수를 여러 개 지정할 수도 있습니다.

function toArray<T, U>(a: T, b: U): [T, U] {
    return [a, b];
}

let o1 = toArray(1, "a"); // 배열 [1, "a"]가 대입

제네릭을 사용하지 않으면 선언해준 데이터 타입으로만 반환값을 선언할 수 있습니다. 즉, 정적입니다. 하지만 제네릭을 사용한다면 동적으로 반환값을 선언할 수 있습니다.

물론 다음과 같이 any 타입으로 동적 선언을 해줄 수도 있습니다.

function toArray(a: any, b: any): any {
    return [a, b];
}

이렇게 타입을 선언한다고 해서 함수의 동작에 문제가 발생하지는 않지만, 타입 검사를 하지 않기때문에 오류를 찾아내는 것이 힘들어집니다. 제네릭을 통해 타입을 선언한다면 타입 검증 과정을 거치기 때문에 좀 더 효율성 있는 개발이 이루어질 수 있을 것입니다.