시리즈의 다른 글 보기 

JavaScript 타입의 부분 계층 구조 출처: Exploring JavaScript (ES2024 Edition)

 

 타입스크립트 심층 분석의 첫 번 째 글에서는 타입스크립트와 타입에 대해 원론적인 내용을 담고 있습니다. 가볍게 시작한다는 마음으로 읽어주시면 좋겠습니다.

 

최근 타입스크립트의 깊이 있는 사용에 대해 관심이 생겼는데요. 사실 일반적인 웹 앱을 개발할 때 타입스크립에 대한 많은 학습이 필요하지 않은 것 같습니다. 하지만 이전에 개발 노트: React navigation에서 덜 귀찮기 위해 더 강력한 타입 사용하기글을 쓴 것처럼 타입을 더 상세하게 적용하여 명확한 코드를 작성하고 싶은 욕심과 라이브러리 개발자들이 타입을 설정하는 것에 대한 궁금증, 그리고 언젠간 직접 라이브러리 개발을 하고 싶다는 욕망까지 많은 이유가 합쳐져 타입 스크립트에 대해 깊이 있게 공부를 시작하였습니다.

타입스크립트의 역할

TypeScript는 JavaScript의 슈퍼셋(Super Set, 상위집합 또는 초집합)인 언어입니다. TypeScript는 JavaScript를 기반으로 JavaScript의 기능들을 제공하면서 그 위에 자체 레이어를 추가합니다. 추가된 레이어가 바로 Type 시스템입니다.

 

JavaScript에서 원시 타입(Primitive Types)과 객체(Object)는 각각의 특성과 동작을 가지고 있지만 각각의 변수에 할당된 타입 혹은 객체가 어떤 특성과 동작을 가지는지 미리 확인해 주지 않습니다. 대신 JavaScript의 다양한 연산을 실행하여 확인하게 됩니다. 간단한 예시로 message라는 이름의 변수에 대하여 실행할 수 있는 몇몇 연산들을 살펴보겠습니다.

message.toLowerCase();
message();

message의 값이 무엇인지 모른다면 위 코드의 실행 결과를 확실히 말할 수 없습니다. 각 연산의 동작은 message가 어떤 값을 가졌는지에 따라 완전히 달라집니다. messagetoLowerCase라는 속성 가졌는지, 만약 가졌다면 toLowerCase는 호출이 가능한지, 또message 자체는 호출이 가능한지를 고려해야 하기 때문입니다.

message가 아래와 같이 정의되었다고 해봅시다.

const message = "Hello World!";

여기서 message.toLowerCase()를 실행하면, 우리는 동일한 문자열이 소문자로만 이루어져 있는 값을 얻을 것입니다. 그리고 message()는 다음과 같은 예외와 함께 실행되지 않을 것입니다.

TypeError: message is not a function

JavaScript 런타임은 코드가 실행될 때 자신이 무엇을 해야 할지 결정하기 위하여 값의 타입, 즉 해당 값이 어떤 동작과 능력을 가지고 있는지를 확인합니다. 이상적으로는, 코드를 실행하기 전에 이러한 버그를 미리 발견할 수 있는 도구가 있다면 좋을 것입니다. TypeScript와 같은 정적 타입 검사기의 역할이 바로 이것입니다.

const message = "hello!";

message();
// 오류 발생
// This expression is not callable.
//   Type 'String' has no call signatures.

TypeScript를 사용한다면 위의 코드를 실행하기 전에 오류를 볼 수 있을 것입니다. 이를 정적 타입 검사라고 하며, 우리가 작성한 코드에서 코드의 실행 전에 타입을 검사하여 해당 코드가 예외나 오류를 발생시킬 수 있는지 미리 확인할 수 있게 해줍니다. TypeScript는 여기서 한 발 더 나아가 비록 오류가 없는 JavaScript 코드, 즉 실행이 가능한 JavaScript 코드라도 잠재적으로 버그로 간줄할 수 있는 코드를 식별하기도 합니다.

 

예를 들어 JavaScript에서 객체에 존재하지 않는 속성(property)에 접근하려고 할 때 오류를 던지는 대신, undefined를 반환합니다. 마찬가지로 할당, 혹은 선언되지 않은 변수 역시 undefined라는 값을 가지는 것처럼 작동합니다. TypeScript는 이를 모두 버그로 간주하여 경고를 표시합니다. 이 외에도 조건에 따라 실행될 수 없는 코드 조각을 판별하거나(아래 예시 참조) 문자와 숫자의 덧셈 등 서로 다른 타입끼리의 연산 혹은 비교 및 존재하지 않는 변수나 속성의 참조(대부분 오타로 인해 발생합니다.)를 모두 잡아냅니다.

const word: "a" | "b" = "a";

if (word !== "a") {
  console.log("word는 a가 아님")
} else if (word === "b") {
  // This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.ts(2367)
  console.log("word는 b")
}

타입 검사기는 우리가 변수 또는 다른 프로퍼티 상의 올바른 프로퍼티에 접근하고 있는지 여부를 검사할 수 있도록 관련 정보들을 가지고 있습니다. 이 정보를 활용하면 타입 검사기는 우리가 사용할 수 있는 프로퍼티를 제안할 수 있습니다. 예를 들어 문자열이 담긴 변수 message의 메서드를 사용하려고 한다면 다음과 같이 사용 가능한 메서드 혹은 접근 가능한 속성을 제안해줍니다.

타입이란?

이제까지 타입스크립트가 무엇을 하는 가에 대해 간략하게 알아보았습니다. 하지만 무언가 놓친 것이 있습니다. 바로 타입이란 도대체 무엇인가에 대한 질문입니다. 이제부터는 이 질문에 대한 답을 시작하도록 하겠습니다.

 

원론적으로 "타입"이란 데이터나 값의 구성과 특성을 정의하는 개념입니다. 타입은 다양한 데이터 값을 분류하고 그 값들이 수행할 수 있는 작업(연산)을 규정하는 역할을 합니다. 타입의 개념은 프로그래밍뿐만 아니라 수학, 논리학 등 여러 분야에서 널리 사용됩니다. 타입의 본질적 의미와 역할을 설명하는 몇 가지 핵심 요소는 데이터의 구분, 연산과 행위, 타입의 규칙, 형식과 추상화로 정리할 수 있습니다.

데이터의 구분

타입은 데이터가 어떤 형태인지, 즉 데이터가 어떤 종류의 값을 가질 수 있는지를 정의합니다. 예를 들어, 숫자, 문자열, 불린 값 등은 서로 다른 타입입니다. 각 타입은 특정한 구조와 속성을 가지고 있으며, 이로 인해 데이터를 분류할 수 있습니다.

  • Number: 산술 연산이 가능하고, 대개 수치 값을 나타냅니다.
  • String: 텍스트 데이터를 나타내며, 문자열 조작이 가능합니다.

연산과 행위

타입은 어떤 데이터에 대해 어떤 연산이 가능한지를 정의합니다. 예를 들어, 숫자형 데이터에는 덧셈, 뺄셈 등의 산술 연산이 가능하고, 문자열에는 연결(concatenation)이나 길이(length) 측정 등의 연산이 가능합니다.

  • 5 + 3, 10 - 2와 같은 산술 연산
  • "Hello" + " World" 같은 문자열 조작

타입의 규칙

타입은 특정 규칙을 정의합니다. 이 규칙은 데이터가 어떻게 처리되고 변형되는지를 설명합니다. 각 타입은 고유한 규칙을 가지며, 이 규칙에 따라 데이터가 처리되거나 연산되며, 데이터가 유효한 연산을 수행할 수 있는지 확인하는 기준을 제공합니다. 특히 타입스크립트에서 중요하게 다루어지는 타입 검사가 바로 각 타입의 규칙을 기반으로 작동합니다.

형식과 추상화

타입은 데이터의 형식을 정의하며, 이를 통해 데이터의 추상화를 가능하게 합니다. 추상화란 복잡한 시스템을 단순한 구성 요소로 나누어 이해하는 과정을 말합니다. 타입은 이 추상화를 통해 데이터의 처리 방식을 명확히 하고, 프로그래머가 데이터의 구조와 사용 방법을 쉽게 이해할 수 있게 합니다.

프로그래밍에서 타입의 구분과 표현

타입의 정의는 프로그래밍에서 데이터가 어떻게 표현되고 조작될 수 있는지를 규정합니다. 다음 몇 가지의 개념으로 각 타입을 정의하고 구분하게 됩니다.

문법적 정의(Syntactic Definition)

문법적 정의는 데이터 타입이 프로그래밍 언어에서 어떻게 선언되는지를 설명합니다. 이는 변수나 함수의 타입을 명시하는 문법적 규칙을 포함합니다. 타입의 정의는 데이터 타입을 구분하고, 프로그래머가 변수나 함수의 타입을 명확히 지정할 수 있도록 합니다. TypeScript의 경우 다음과 같이 데이터의 타입을 정의합니다.

let age: number; // 'age'는 숫자 타입
let name: string; // 'name'은 문자열 타입

표현적 정의(Representation Definition)

표현적 정의는 데이터 타입의 메모리 표현을 설명합니다. TypeScript는 변수나 객체의 메모리 표현을 구체적으로 정의하지 않지만, 자바스크립트에서의 타입 표현 방식에 따라 타입을 정의합니다. 예를 들어, number 타입은 자바스크립트에서 64비트 부동 소수점 숫자로 표현됩니다. 이러한 정의는 컴퓨터가 데이터를 물리적으로 어떻게 저장하고 처리하는지를 이해하는 데 중요합니다.

표현 및 동작(Representation and Behaviour)

표현 및 동작은 데이터 타입에 대해 지원하는 연산자와 메서드를 규정합니다. 객체 지향 프로그래밍에서 클래스는 데이터(속성)와 동작(메서드)을 정의하며, 이는 데이터 타입의 동작을 설명하는 예시입니다. 예를 들어, Number 타입은 덧셈(+), 뺄셈(-), 곱셈(*)과 같은 연산을 지원합니다. 이 정의는 데이터가 어떻게 조작될 수 있는지를 명확히 하며, 프로그래머가 적절한 연산을 선택할 수 있도록 도와줍니다.

const num1 = 12;
const num2 = 2;
num1 - num2; // 가능

const str1 = "12";
const str2 = "2";
str1 = str2; // 불가능, string에 빼기(-)연산은 지원되지 않음

값의 집합(Value Space)

값의 집합 정의는 데이터 타입이 허용하는 모든 가능한 값의 집합을 규정합니다. 이는 데이터 타입이 표현할 수 있는 모든 값을 설명합니다. 쉽게 말해, 각각의 타입은 가질 수 있는 고유의 값 범위가 존재합니다. 예를 들어 Boolean 타입은 true와 false라는 값들만을 가지고 있습니다.

let boolValue: boolean;
boolValue = true; // 가능
boolValue = "no"; // 불가능. "no"는 boolean의 범위에 포함되지 않음

값의 집합과 동작(Value Space and Behaviour)

값의 집합(Value Space)과 표현 및 동작(Representation and Behaviour)을 결합한 개념입니다. 제공하는 값의 범위와 이들에 대해 수행할 수 있는 연산을 설명합니다. 타입스크립트는 각 타입이 가질 수 있는 값의 집합과 그 값들이 사용가능한 연산자, 메서드, 속성을 이용해 타입 검사를 실행하게 됩니다. 타입 시스템이 코드의 정확성을 검증하는 데는 각 타입에 어떤 값들이 들어갈 수 있고 그 값들이 지원하는 연산자와 메서드를 알고 있어야 한다는 것입니다.

결론

정리하자면 프로그래밍에서는 타입은 값의 집합과 그 값이 가진 동작 및 속성을 나타내는 개념이입니다. 예를 들어, number 타입은 모든 숫자 값을 포함하며, 이 타입의 값들은 수학적 연산을 수행할 수 있습니다. 또한, object 타입은 속성과 메서드를 가진 복합적인 구조를 정의하며, 이는 객체의 상태와 행동을 설명합니다.

 

프로그래밍에서 이렇게 정의된 값 자체와 속성, 할 수 있는 행동을 추적하고 안정성을 확보할 수 있도록 하는 것이 타입 시스템입니다. 이를 활용하면 (주로 변수에 담긴)값의 안정적인 사용(타입안정성)을 보장하고 코드 작성 시 자동 완성과 오류 감지를 통해 개발자의 생산성을 높여줍니다.

 

타입은 프로그래밍에서 값의 가능한 집합을 정의하고, 그 값이 어떻게 동작하며 어떤 모습을 가질 수 있는지를 구체화하여, 코드를 안전하고 예측 가능하게 만드는데 중요한 역할을 합니다.

참고 자료

TypeScript, The Basics
위키백과, Data type

Exploring JavaScript (ES2024 Edition), 14.Values

+ Recent posts