[타입스크립트] 기본 개념

Woonil·2025년 4월 28일
0

타입스크립트

목록 보기
1/4
post-thumbnail

타입스크립트는 자바스크립트 언어 상에서 설계된 강타입의 프로그래밍 언어로, 타입 안정성을 제공함으로써 런타임 에러를 줄이고 결국 생산성을 높인다.

  • 타입시스템: 타입과 관련한 규칙을 모아둔 체계이며, 대부분의 프로그래밍 언어들은 이를 가지고 있다.
    • 정적 타입 시스템: 코드 실행 이전 모든 변수의 타입을 고정적으로 결정한다. (엄격하고 고정적인 시스템) ex) C, Java
    • 동적 타입 시스템: 코드 실행 이후 유동적으로 변수의 타입을 결정한다. (자유롭고 유연한 시스템) ex) Python, JavaScript

타입스크립트는 실행 전 검사를 통한 타입 안정성을 확보하여 정적 타입 시스템의 엄격함과, 변수에 일일이 타입을 지정해주지 않아도 되는 동적 타입 시스템의 유연함을 동시에 갖추었다.

let a = "hello"
let b : boolean = "hi" // 명시적 선언
  • 특징
    • 자바스크립트는 파라미터 개수를 다르게 지정해도 에러를 던지지 않는 경우, 런타임에서야 에러를 던지는 경우와 같이 개발자의 실수로부터 발생하는 프로그래밍 오류를 잡아내지 않는다.
    • 타입스크립트는 에러가 발생할 것 같은 코드를 감지하면 자바스크립트로 컴파일 자체를 하지 않는다. 즉, 자바스크립트는 코드를 실행하는 런타임에 타입을 결정, 타입스크립트는 컴파일 시 타입이 결정되고 오류가 발견된다.
    • 오류 예방: 컴파일 단계에서 타입을 검사하여 실행 단계에서 발생할 수 있는 오류를 사전에 발견하고 수정할 수 있다.
    • 코드 가독성 및 유지보수성 향상: 타입을 명시적으로 지정함으로써 코드의 의미를 명확하게 표현할 수 있고, 코드의 재사용성과 유지보수성을 높일 수 있다.
    • 협업 효율성 향상: 타입을 통해 코드의 의도를 명확하게 전달할 수 있어서 협업의 효율성을 증대시킨다.
    • 자바스크립트의 호환: 타입스크립트와 자바스크립트는 100% 호환되기 때문에 자바스크립트가 사용되는 어떤 곳이든 타입스크립트를 사용할 수 있다.
    • 컴파일 방식: 타입스크립트의 컴파일 결과물은 바이너리 코드가 아닌 자바스크립트 파일이다. 이러한 면에서 타입스크립트를 자바스크립트에 타입이라는 레이어를 끼얹은 일종의 템플릿 언어 또는 확장 언어로 해석하는 견해도 있다.

🤔개념

Primitive Type & Object Type

자바스크립트에서 값은 타입을 가지지만, 변수는 별도의 타입을 가지지 않는다. 따라서 자바스크립트의 변수에는 어떤 타입의 값이라도 자유롭게 할당할 수 있다. 타입스크립트는 이 변수에 타입을 지정할 수 있는 타입 시스템 체계를 구축했다. 자바스크립트의 7가지 원시 값은 타입스크립트에서 원시 타입으로 존재한다.

타입스크립트의 타입은 아래와 같은 계층구조를 가진다. 타입스크립트의 각 타입이 가지는 의미를 자세히 살펴본다. '우아한 타입스크립트', '얄팍한 코딩사전: 타입스크립트', '한 입 크기로 잘라먹는 타입스크립트: 이정환' 를 참고했다.

Primitive Type

타입명설명대응하는 자바스크립트 원시 래퍼 객체
booleantrue, false 값만 할당할 수 있다. 형 변환을 통해 Truthy 또는 Falsy로 평가되는 값은 이 타입에 해당되지 않음에 주의한다.Boolean
undefinedundefined 값만 할당할 수 있으며, 초기화되지 않았거나 존재하지 않음을 의미한다. optional로 지정된 타입 내 속성에도 할당될 수 있다. (아직 여기 뭘 넣을지 모르겠어)Undefined
nullnull 값만 할당할 수 있으며, undefined와는 엄연히 다른 원시 값이기 때문에 서로의 타입에 할당할 수 없다. (비워둘 상자)Null
number숫자에 해당하는 모든 원시 값을 할당할 수 있다.Number
bigintNumber.MAX_SAFE_INTEGER(2^53-1)를 넘어가는 값을 처리할 수 있다.BigInt
string문자열을 할당할 수 있으며, 템플릿 리터럴(백틱(`)으로 감싼 문자열)도 할당할 수 있다.String
symbol유일무이한 성질을 갖는 값을 생성할 때 사용한다.Symbol

strictNullChecks

tsconfig 파일의 compilerOptions의 strictNullChecks 옵션을 true로 설정하면, null이나 undefined가 아닌 변수에 null이나 undefined를 할당하면 에러를 발생시킨다.

Object Type

7가지 원시 타입에 속하지 않는 값은 모두 객체 타입으로 분류할 수 있다.

타입명설명
object객체, 배열, 정규 표현식, 함수, 클래스 등 모두 호환된다.
{}객체 리터럴 방식으로 타입을 지정하며, 타이핑되는 객체가 중괄호 내의 선언 구조와 일치해야 한다.
array배열 내 모든 원소들은 동일한 하나의 타입 값만 가질 수 있다.
type & interface{}로 타입을 매번 일일이 지정하는 것은 반복적인 작업을 수반하므로, 보통 객체를 타이핑할 때는 이 키워드들을 사용한다.
함수(타입명 없음)객체의 타이핑과 달리, typeof 연산자로 확인한 function이라는 키워드 자체를 타입으로 사용하지 않으며, 매개변수를 별도의 타입으로 지정해야 한다.

object

특정 변수가 객체라는 것만 알 수 있어 속성 이름이나 타입 정보를 알 수 없다. 따라서 특정 속성 접근 시 타입 안정성이 보장되지 않는다. 결국 원시 타입을 제외한 모든 객체 타입을 유동적으로 할당할 수 있기 때문에 정적 타이핑의 의미가 퇴색되므로, 사용하지 않을 것을 권장한다.

  • 객체 리터럴({}): 객체의 모든 프로퍼티에 대해 타입을 정의할 수 있다.
    const player : {
      name: string,
      age: number
    } = {
      name: "wonil"
    	age: 25
    }
  • Optional Property
    const player : {
        name: string,
        age?: number
    } = {
        name: "wonil"
    }
  • type Alias(타입 별칭): 타입을 마치 변수처럼 사용할 수 있게 한다. 같은 객체 리터럴 타입을 가진 여러 객체를 생성하고 싶을 때, 반복적인 객체 리터럴 선언을 줄여 재사용성을 높인다.
    type Player = {
        name: string,
        age?: number,
    }
    
    const wonil : Player =  {
        name: "wonil"
    }
    const nico : Player =  {
        name: "nick"
    }
  • call signature: 함수 자체의 타입을 알려주는 역할을 하며, 화살표 함수 방식으로만 작성된다. 함수의 매개변수와 반환 타입을 미리 선언한다.
    function add(a:number, b:number) {
        return a + b
    }
    const add = (a:number, b:number) => a + b // Arrow function
    
    // 매번 매개변수의 타입을 지정해주는 대신 함수자체의 타입을 미리 지정하여 재활용함
    type Add = (a:number, b:number) => number;
    // long version
    type Add = {
        (a:number, b:number) : number
    }
    const add:Add = (a, b) => a + b
  • Array<자료형> / 자료형[] : 배열에 특정 자료형의 원소만을 가지게 강제한다.
    const array: Array<number> = [1, 2, 3];
    const array: number[] = [1, 2, 3];

다차원 배열의 타입

다차원 배열도 자료형[][] (2차원 배열), 자료형[][][] (3차원 배열)과 같이 타입을 지정할 수 있다.

읽기 전용 배열

읽기 전용 배열이란 사용할 수는 있지만 수정하지는 못하는 배열이다. readonly 키워드나 제너릭으로 읽기 전용으로 배열을 생성한다.

type Player = {
    readonly name: string,
    age?: number,
};

const numbers: readonly number[] = [1, 2, 3, 4];
// 또는 const numbers: readonly ReadonlyArray<number> = [1, 2, 3, 4];
numbers.push(5); // Error
  • Tuple: 배열 타입의 하위 타입으로, 기존 타입스크립트의 배열 기능에 ‘길이 제한’까지 추가한 타입 시스템으로, 이는 지정석이 있는 열차에 비유할 수 있다.
    const player: [string ,number, boolean] = ["wonil", 1, true]
    player[0] = 1 // Error
     

Advanced Type

any

타입스크립트의 타입 검사를 비활성화하여 보호장치를 완전히 없애므로, 자바스크립트에 존재하는 모든 값을 오류 없이 받을 수 있다.

 const a : any[] = [1, 2, 3, 4]
  • any 타입을 어쩔 수 없이 사용해야 할 때
    • 개발 과정에서 변경될 가능성이 있거나 아직 세부 항목에 대한 타입이 확정되지 않은 경우

    • 어떤 값을 받아올지 또는 넘겨줄지 정할 수 없을 때

    • 값을 예측할 수 없을 때 암묵적으로 사용

      이런 예외적인 상황이 있음에도 불구하고, any 타입은 사실상 런타임에 타입을 검사하므로 타입스크립트를 사용하는 의미가 퇴색된다. 따라서 사용을 지양하는 것이 좋다.

unknown

무엇이 할당될지 아직 모르는 상태의 타입으로, 이 타입으로 할당된 변수는 어떤 값이든 올 수 있음을 의미하는 동시에 개발자에게 엄격한 타입 검사를 강제하는 의도를 담고 있다. 즉, any 타입을 사용함으로써 문제가 되는 상황을 보완하기 위해 등장했다고 볼 수 있다. 타입 검사를 강제하고 타입이 식별된 후에 사용할 수 있기 때문에 any 타입보다 안전하다고 볼 수 있다. 따라서 데이터 구조 파악이 힘들 때 any 대신 unknown 사용을 권장한다.

let a: unknown;

let b = a + 1 // Error
if (typeof a === 'number') {
    let b = a + 1
}

let b = a.toUpperCase() // Error
if (typeof a === "string") {
    let b = a.toUpperCase()
}

하지만 unknown은 any 타입을 제외한 다른 타입으로 선언한 변수에 할당할 수 없다. 가령, 아래와 같은 경우 fileItem이 명시적으로 객체로 선언되지는 않았지만, in 키워드로 인해 객체로 추론된다. 따라서 fileItem의 타입으로 unknown을 쓰면 Type 'unknown' is not assignable to type 'object'.와 같은 에러를 볼 것이다.

// TYPE GUARD: 게시물 수정 시 기존 파일 여부 체크
const isExistingFileDto = (fileItem: any): fileItem is IExistingFileDto => {
    return ORIGINAL_FILE_FLAG in fileItem;
};
anyunknown
어떤 타입이든 any 타입에 할당 가능어떤 타입이든 unknown 타입에 할당 가능
any 타입은 never 타입을 제외한 어떤 타입으로도 할당 가능unknown 타입은 any 타입 외에 다른 타입으로 할당 불가능

void

만나도 줄 선물이 없는 산타

함수가 어떤 값을 반환하지 않는 경우에 사용되며, 변수의 경우에는 undefinednull 값만 할당할 수 있지만 잘 사용되지 않는다. 사실, 함수의 경우에도 함수 내부에 별도 반환문이 없는 경우에는 타입스크립트 컴파일러가 알아서 함수 타입을 void로 추론한다.

function voidFunc(): void {
  console.log("This is a function in chapter7.ts");
}
  
function func(): void {
  console.log("This is a function.");
  return undefined;
}

function anotherFunc(): undefined {
  console.log("This is another function.");
  return;
}

function anotherFunc(): undefined {
  console.log("This is another function.");
  return undefined ;
}

never

절대 만날 수 없는 산타

일반적으로 함수와 관련하여 많이 사용되며, 값을 ‘반환할 수 없는’ 타입이다.

function hello(name:string|number) {
    if (typeof name === "string") {
    } else if (typeof name === "number") {
    } else {
        name // never => 도달하면 안되는 코드(name의 타입은 string이나 number 중에서 정해져야 함)
    }
}
  • 값을 반환할 수 없는 예
    • 에러를 던지는 경우: 정상 종료되지 않고, 값을 반환하는 과정에 도달하지 못한다.
          function generateError(res: Response): never {
          	throw new Error(res.getMessage());
          }
    • 무한히 함수가 실행되는 경우
          function checkStatus(): never {
          	while (true) {
          		...
          	} 
          }
      
  • 사용하는 이유: 자료형에 따라 다른 값을 반환하는 함수를 작성해야 한다고 가정하자. 요구사항에 object 타입도 들어올 수 있다고 명시되어 있지만, 만약 개발자가 이를 깜빡하고 함수 내부에서 처리하지 않았을 때 런타임에서야 해당 오류가 발생할 것이다. 이는 실제 서비스라면 매우 치명적일 수 있다. 따라서 함수의 매개변수에 타입을 미리 지정하고 never를 처리하는 코드를 작성함으로써 컴파일 시점에 에러를 잡아낼 수 있다.
    function handleValue(x:string|number|boolean|object) {
        if (typeof x === 'string') {
            console.log(x.toUpperCase());
        } else if (typeof x === 'number') {
            console.log(x.toFixed(2));
        } else if (typeof x === 'boolean') {
            console.log(x ? 'true' : 'false');
        } else {
    				// 아랫줄에서 오류가 발생
    				// Type 'object' is not assignable to type 'never'.
            const _exhaustiveCheck: never = x; 
            console.log(_exhaustiveCheck);
        }
    }

enum

열거형이라도 부르며, 일종의 구조체를 만드는 타입 시스템이다. 주로 문자열 상수를 생성하는 데 사용된다.

enum Os {
	Window = 3, 
	Ios = 11,
	Android = 'win'
}

console.log(Os['Ios']) // 11
console.log(Os[11]) // "Ios"
  • 특징
    • 인덱스를 지정하지 않으면 디폴트로 0부터 부여된다.
    • 숫자를 부여하면 양방향 매핑이 가능하다. (문자열은 단방향 매핑만 지원)
    • 응집력있는 집합 구조체를 만들 수 있다.

const enum

숫자로만 이루어졌거나 타입스크립트가 자동으로 추론한 열거형은 할당된 값을 넘어선 범위로 역방향 접근을 하더라도 에러를 발생시키지 않는다. 따라서, 이러한 동작을 막기 위해서 const enum 을 사용한다.

💱타입스크립트 컴파일

타입스크립트는 tsc라고 불리는 컴파일러를 통해 자바스크립트 코드로 변환된다. 하지만 타입스크립트는 고수준 언어가 저수준 언어로 변환되는 것이 아닌, 고수준 언어(타입스크립트)가 또 다른 고수준 언어(자바스크립트)로 변환되는 것이므로 컴파일이 아닌 트랜스파일(Transpile)이라 부르기도 한다. 또한 소스코드를 다른 소스코드로 변환한다는 관점에서 소스 대 소스 컴파일러(Source-to-Source Compiler)라고도 한다.

컴파일(Compile)

개발자가 작성한 소스코드를 컴퓨터가 이해할 수 있는 바이트 코드로 변환하는 과정

타입스크립트 컴파일러(tsc)의 소스코드 컴파일 후 실행까지의 과정

  1. [tsc] 타입스크립트 소스코드를 타입스크립트 AST(Abstract Syntax Tree, 최소 구문 트리)로 만든다.

  2. [tsc] 타입 검사기가 AST를 확인하여 타입을 검사(Type Checking)한다.

    타입스크립트 소스 코드의 타입은 1~2단계에서만 사용되며, 최종적으로 만들어지는 프로그램에는 아무런 영향을 주지 않는다.

  3. [tsc] 검사 성공 시, 타입스크립트 AST를 자바스크립트 소스로 변환한다(검사 실패 시에는 컴파일 종료됨). 이때, 타입과 관련한 코드들은 컴파일 결과 모두 사라진다.

  4. [런타임] 자바스크립트 소스코드를 자바스크립트 AST로 만든다.

  5. [런타임] AST가 바이트 코드로 변환된다.

  6. [런타임] 런타임에서 바이트 코드가 평가되어 프로그램이 실행된다.

AST(Abstract Syntax Tree, 추상 문법 트리)

컴파일러가 소스 코드를 해석하는 과정에서 생성된 데이터 구조이며, 어휘적 분석과 구문 분석을 통해 소스 코드를 노드 단위의 트리 구조로 구성한다.

profile
프론트 개발과 클라우드 환경에 관심이 많습니다:)

0개의 댓글