Menu

TypeScript null과 undefined: 체크 방법, ?.와 ?? 연산자

strictNullChecks가 켜져 있으면 null과 undefined는 별도의 타입이고 TypeScript는 이를 처리하게 만듭니다. null과 undefined를 확인하는 방법, 옵셔널 체이닝(?.), 물음표 두 개(??)와 ??=, 선택적 속성과 | undefined의 차이를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

strictNullChecks가 켜져 있으면(strict에 포함됩니다) null과 undefined는 각각 자체 타입입니다. string은 절대 null이 될 수 없고, 없을 수도 있는 값은 string | null처럼 타입에 그 사실을 밝혀야 하며, TypeScript는 값을 쓰기 전에 그 경우를 처리하게 만듭니다.

검사 뒤에 컴파일러는 name이 string이라는 것을 알므로 .split이 허용됩니다. 이 페이지의 나머지는 그런 검사를 하는 방법과 검사를 짧게 만드는 연산자를 다룹니다.

strictNullChecks와 "possibly null" 오류

타입에 null이나 undefined가 포함되어 있으면 TypeScript는 값이 항상 있는 것처럼 쓰는 것을 거부합니다.

index.ts(3,22): error TS18047: 'name' is possibly 'null'.

undefined 버전은 TS18048, 'x' is possibly 'undefined'입니다. strictNullChecks가 없으면 null과 undefined가 모든 타입에 허용되어 이 코드는 컴파일되고, name이 처음 null일 때 런타임에 TypeError로 멈춥니다. 이런 종류의 버그가 strict를 켜 두는 가장 큰 이유입니다.

일상적인 코드에서 이런 타입이 생기는 곳:

출처타입
arr.find(...)T | undefined
map.get(key)V | undefined
선택적 속성 p?: T읽을 때 T | undefined
선택적 매개변수 x?: T함수 안에서 T | undefined
str.match(re)RegExpMatchArray | null
JSON.parse(text)any, 그래서 아무것도 검사되지 않음

null과 undefined 확인하기

아래의 모든 검사는 블록 안에서 타입을 좁힙니다. 배제하고 싶은 것에 맞는 검사를 고르세요.

검사타입에서 없애는 것
x !== undefinedundefined
x !== nullnull
x != nullnull과 undefined
typeof x !== "undefined"undefined
if (x)null과 undefined, 그리고 값 0, "", false, NaN도 건너뜀

== null은 느슨한 동등 비교가 관용적으로 쓰이는 유일한 곳입니다. 정확히 null과 undefined일 때만 참이고 다른 값에는 거짓입니다. 참 같은 값 검사였다면 빈 문자열을 없는 값으로 취급했을 것이고, 이는 흔한 버그입니다.

옵셔널 체이닝: ?.

a?.b는 a가 null이나 undefined가 아니면 b를 읽고, 그렇지 않으면 멈추고 undefined를 반환합니다. 같은 연산자를 인덱스 a?.[i]와 호출 fn?.()에도 쓸 수 있습니다.

bob.address?.city의 타입은 string | undefined입니다. 옵셔널 체이닝은 결과에 undefined를 더하므로 보통 ??와 함께 씁니다. 런타임 동작은 평범한 JavaScript이며, 단락 평가의 세부 사항은 옵셔널 체이닝을 참고하세요.

물음표 두 개: ??

a ?? b는 a가 null이나 undefined가 아니면 a를, 맞으면 b를 반환합니다. 0, "", false, NaN까지 버리는 예전 관용구 a || b를 대체합니다.

왼쪽 값left || "d"left ?? "d"
null"d""d"
undefined"d""d"
0"d"0
"""d"""
false"d"false
NaN"d"NaN

타입에서 ??는 왼쪽에서 null과 undefined를 없애고 나머지를 오른쪽과 합칩니다. 그래서 scores.get("Linus") ?? 0을 number에 대입할 수 있습니다.

널 병합 대입: ??=

a ??= b는 a가 null이나 undefined일 때만 a에 b를 대입합니다. 형제 연산자 ||=와 &&=는 왼쪽이 falsy이거나 truthy일 때 대입합니다.

retries: 0은 ??=에서 살아남고, 빈 label은 ||=로 대체됩니다. opts.retries ??= 3 뒤로 TypeScript는 함수의 나머지 부분에서 opts.retries를 number로 좁힙니다.

선택적 속성과 | undefined

nickname?: string과 nickname: string | undefined는 읽을 때는 같지만, 키가 반드시 있어야 하는지가 다릅니다.

호출하는 쪽이 속성을 빼도 된다면 ?를, 값이 undefined라도 모든 호출자가 명시적으로 넘기기를 원한다면 | undefined를 쓰세요. exactOptionalPropertyTypes 옵션(strict에 포함되지 않음)은 ?를 더 엄격하게 만듭니다. 그러면 nickname?: string은 { nickname: undefined }를 거부하고, 키가 없거나 문자열인 경우만 받습니다. 선택적 함수 매개변수(x?: number)도 선택적 속성처럼 동작해서, 함수 안에서 x는 number | undefined입니다.

null과 undefined 중 무엇을 쓸까

TypeScript가 선택을 강요하지는 않지만, 한 코드베이스에서 둘을 섞어 쓰면 모든 검사가 두 경우를 처리해야 합니다. 흔한 관례:

  • 자신의 타입에서 "설정되지 않음"에는 undefined(와 선택적 속성)를 쓰세요. JavaScript가 기본으로 만드는 값입니다. 없는 속성, 생략한 인수, find와 Map.get이 찾지 못한 경우가 그렇습니다.
  • API가 null을 줄 때는 받아들이세요. JSON에는 undefined가 없고, String.prototype.match와 많은 DOM 메서드는 null을 반환합니다.
  • 값이 둘 중 무엇이든 될 수 있으면 == null로 확인하세요.

배열 인덱싱은 유일한 빈틈입니다. users[5]는 인덱스가 범위를 벗어나도 요소 타입으로 지정됩니다. noUncheckedIndexedAccess 옵션(strict에 포함되지 않음)은 모든 인덱스 접근에 | undefined를 더해서 컴파일러가 이것도 잡게 합니다.

자주 묻는 질문

TypeScript에서 물음표 두 개(??)는 무슨 뜻인가요?

a ?? b는 JavaScript의 널 병합(nullish coalescing) 연산자입니다. a가 null이나 undefined가 아니면 a를, 맞으면 b를 반환합니다. ||와 달리 0, "", false 같은 다른 falsy 값은 유지합니다. TypeScript는 왼쪽의 타입에서 null과 undefined를 없애므로, a가 string | undefined일 때 a ?? "x"는 string입니다.

TypeScript에서 값이 undefined인지 어떻게 확인하나요?

비교하세요: if (value !== undefined) { ... }. TypeScript가 블록 안에서 타입을 좁힙니다. null과 undefined를 한 번에 배제하려면 느슨한 동등 비교 value != null을 쓰세요. ==/!=가 관용적으로 쓰이는 유일한 곳입니다. 참 같은 값 검사(if (value))도 좁히지만 0, "", false를 건너뜁니다.

"Object is possibly undefined"는 무슨 뜻인가요?

오류 TS18048('x' is possibly 'undefined')과 TS18047('x' is possibly 'null'), 또는 getUser().address.city처럼 값에 단순한 이름이 없을 때의 TS2532(Object is possibly 'undefined')는 strictNullChecks에서 나옵니다. 타입에 undefined나 null이 포함되어 있는데 코드가 그럴 리 없다는 듯이 값을 썼다는 뜻입니다. 먼저 확인하거나, 옵셔널 체이닝(x?.name)을 쓰거나, ??로 기본값을 주거나, 값이 정말 빠질 수 없다면 타입을 바꾸세요.

TypeScript에서 null과 undefined의 차이는 무엇인가요?

각각 값이 하나뿐인 별개의 두 타입입니다. JavaScript는 설정된 적 없는 것(없는 속성, 생략한 인수, 없는 키에 대한 Map.get)에 undefined를 쓰고, API는 의도적인 "값 없음"에 null을 씁니다(JSON, 많은 DOM 메서드). TypeScript는 둘을 따로 추적하므로 string | null은 undefined를 받지 않습니다. 많은 코드베이스가 자체 코드에는 undefined를 쓰고 null은 경계에서만 받아들입니다.

선택적 속성은 | undefined와 같은가요?

완전히 같지는 않습니다. name?: string은 속성이 아예 없을 수도 있다는 뜻이고, 읽으면 string | undefined가 됩니다. name: string | undefined는 값이 undefined여도 속성 자체는 반드시 있어야 한다는 뜻입니다. exactOptionalPropertyTypes가 켜져 있으면 name?: string은 명시적인 undefined 값도 받지 않습니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기