🔥 원시 타입: string/number/boolean/bigint/symbol

909자
10분

TypeScript 의 원시 다섯 가지(string · number · boolean · bigint · symbol)와 그 아래 별도 단위로 놓인 null · undefined · void 를 한 줄에 정리한 표지 일러스트

작은 한 줄에 손이 멈췄다

bunx tsc --noEmit 을 돌렸을 때 나는 nullstring 에 들어가지 않는다는 에러에서 잠깐 멈췄다. 내 머릿속에서는 문자열 값이 없다는 뜻이면 null 도 당연히 들어갈 것 같았다. 그런데 TS 6.0 strict 기본에서는 그 줄을 그냥 넘기지 않았다. 내가 원시 타입을 다시 살핀 이유는 이 지점이었다. string, number, boolean 처럼 쉬워 보이는 이름도 실제로는 값의 범위를 구체적으로 제한한다.

원시 타입은 객체가 아닌 값의 기본 단위다. TypeScript 에서 먼저 익힐 값은 string, number, boolean, bigint, symbol 다섯 가지다. ECMAScript 사양에는 nullundefined 도 원시 값에 들어가지만, 두 값은 strict 기본에서 다른 타입에 자동으로 들어가지 않는 별도 검사 대상이다. 콜론으로 타입을 적는 표기는 앞에서 이미 다뤘다.

다섯 가지 원시 타입

string / number / boolean / bigint / symbol 다섯 가지 원시 타입 패널에 각각의 예시 값을 한 줄씩 적어 정리한 일러스트

다섯 가지 원시 타입은 값의 종류를 직접 제한한다. 문자열은 string, 숫자는 number, 참거짓은 boolean 으로 받는다. 큰 정수는 bigint 로 받는다. 매번 새로 만드는 고유한 값은 symbol 로 받는다.

ts
// file: primitives.ts
export {};
 
const title: string = "TypeScript";
const score: number = 99.5;
const isPublished: boolean = true;
const largeId: bigint = 9007199254740993n;
const uniqueKey: symbol = Symbol("user-id");
 
console.log(title, score, isPublished, largeId, uniqueKey);
ts
// file: primitives.ts
export {};
 
const title: string = "TypeScript";
const score: number = 99.5;
const isPublished: boolean = true;
const largeId: bigint = 9007199254740993n;
const uniqueKey: symbol = Symbol("user-id");
 
console.log(title, score, isPublished, largeId, uniqueKey);

string 은 따옴표로 감싼 텍스트를 받는다. number 는 정수와 소수를 함께 받는다. booleantruefalse 두 값만 받는다. 이 셋은 자바스크립트 코드에서 가장 자주 나온다.

bigint 는 정수 전용 타입이다. 리터럴 끝에 n 을 붙여 1n 처럼 적는다. number 와 섞어 계산하지 않는다. 큰 식별자나 정밀한 정수 계산처럼 소수점이 필요 없는 값에 맞는다.

number 와 bigint 의 값 모양 차이를 두 패널로 비교한 일러스트 — 왼쪽은 99.5 / 42 / 3.14 처럼 정수와 소수가 함께 들어가는 number, 오른쪽은 1n / 9007199254740993n / 0n 처럼 n 접미사로 적는 정수 전용 bigint

symbol 은 고유한 키가 필요할 때 사용한다. Symbol("user-id") 의 문자열은 설명일 뿐이고, 같은 설명을 넣어도 새 값은 서로 다르다. 그래서 일반 문자열 키와 달리 충돌을 피하는 데 쓴다.

같은 설명 문자열 Symbol("user-id") 로 만든 세 카드가 각각 다른 해시 값을 들고 옆 카드와 != 부등호로 이어져 있어 충돌 없는 고유 키 용도임을 보여주는 일러스트

null 과 undefined 는 자동으로 섞이지 않는다

strict 기본에서 string 변수에 null 을 직접 대입하면 막히고, string 또는 string | null 처럼 유니온으로 받으면 통과하는 차이를 두 패널로 비교한 일러스트

nullundefined 는 값이 없다는 상황을 표현하지만, strict 기본에서는 다른 타입에 자동으로 들어가지 않는다. string 이라고 적은 변수에는 문자열만 들어간다. 값이 없을 가능성까지 받으려면 타입에 그 가능성을 직접 적어야 한다.

ts
// file: null-vs-undefined.ts
export {};
 
let displayName: string = "Mejoo";
// displayName = null;
 
let nullableName: string | null = null;
let optionalName: string | undefined = undefined;
 
function loadName(found: boolean): string | null {
  if (found) {
    return "Mejoo";
  }
 
  return null;
}
 
console.log(displayName, nullableName, optionalName, loadName(false));
ts
// file: null-vs-undefined.ts
export {};
 
let displayName: string = "Mejoo";
// displayName = null;
 
let nullableName: string | null = null;
let optionalName: string | undefined = undefined;
 
function loadName(found: boolean): string | null {
  if (found) {
    return "Mejoo";
  }
 
  return null;
}
 
console.log(displayName, nullableName, optionalName, loadName(false));

주석을 풀어 displayName = null 을 넣으면 TS 6.0 strict 기본에서 컴파일러가 멈춘다. 메시지는 Type 'null' is not assignable to type 'string'. 이다. 이 메시지는 null 값을 string 변수에 넣을 수 없다는 뜻이다. strictNullChecks 가 이 차이를 실제 검사로 다룬다. TypeScript 는 stringnull 을 별개의 타입으로 본다.

값 없음까지 허용하려면 유니온 타입을 쓴다. string | null 은 문자열 또는 null 을 받겠다는 뜻이다. string | undefined 도 같은 방식으로 읽는다. 이 표기는 값이 없을 수 있는 함수 반환값, 아직 채워지지 않은 상태값, 외부 입력을 다룰 때 중요하다.

nullundefined 를 같은 뜻으로 섞으면 분기 조건이 모호하다. null 은 보통 일부러 비워 둔 값에 맞고, undefined 는 아직 값이 없거나 프로퍼티가 빠진 상황에 맞다. 프로젝트마다 규칙은 다를 수 있지만, 타입에는 허용 범위를 직접 적어야 한다는 점은 같다.

void 는 반환 타입에서 읽는다

콜백 시그니처가 void 일 때 호출자가 반환값을 타입 차원에서 무시하는 흐름과, void 로 선언한 함수에서 직접 값을 반환하면 컴파일러가 막는 동작을 두 패널로 비교한 일러스트

void 는 값의 종류라기보다 반환값을 쓰지 않겠다는 약속에 가깝다. 함수가 호출자에게 의미 있는 값을 돌려주지 않을 때 (): void 라고 적는다. 그래서 변수에 담을 값의 타입으로 익히기보다 함수 시그니처에서 먼저 읽는 편이 낫다.

ts
// file: void-fn.ts
export {};
 
type LogLine = (message: string) => void;
 
const log: LogLine = (message) => {
  console.log(message);
  return message.length;
};
 
function announce(message: string): void {
  console.log(message);
  // return message.length;
}
 
log("saved");
const result = announce("ready");
console.log(result);
ts
// file: void-fn.ts
export {};
 
type LogLine = (message: string) => void;
 
const log: LogLine = (message) => {
  console.log(message);
  return message.length;
};
 
function announce(message: string): void {
  console.log(message);
  // return message.length;
}
 
log("saved");
const result = announce("ready");
console.log(result);

LogLine 은 콜백의 반환값을 호출자가 쓰지 않는다는 뜻이다. 그래서 콜백 본문이 숫자를 반환해도 호출 결과는 TypeScript 에서 void 로 취급되어 사용할 수 없다 (자바스크립트 런타임은 그 값을 그대로 반환하지만, 타입 검사 단계에서는 보이지 않는다). 반면 announce 처럼 함수 선언에 : void 를 직접 붙이면 본문에서 의미 있는 값을 반환할 수 없다. 주석을 풀면 Type 'number' is not assignable to type 'void'. 메시지가 나온다. 이 메시지는 숫자 반환값을 void 반환 타입으로 볼 수 없다는 뜻이다.

voidundefined 는 같은 말이 아니다. undefined 는 실제 값이고 타입 이름이기도 하다. void 는 반환값을 신경 쓰지 않는 함수 계약에 더 가깝다. 그래서 이벤트 핸들러, 로그 함수, 콜백 타입에서 자주 나온다.

작은 타입 이름이 큰 검사를 만든다

원시 타입 이름은 짧지만 검사 범위는 분명하다. string, number, boolean 은 일상적인 값의 대부분을 받는다. bigint 는 큰 정수 계산을 따로 분리한다. symbol 은 고유한 키를 만든다.

nullundefined 는 strict 기본에서 원시 타입 안으로 자동 편입되지 않는다. 값 없음이 정상 입력이라면 string | null 처럼 허용 범위를 직접 넓힌다. void 는 반환 타입에서 읽는다. 이 기준을 잡아 두면 변수 선언과 함수 시그니처에서 타입 이름이 훨씬 또렷하다.

YouTube 영상

채널 보기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
투영과 예측, 그리고 선형 결합 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
트라이(Trie)에서 단어를 삭제하는 방법 | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
벡터의 정의와 덧셈 연산 | 선형대수학