2023-03-16 · 17 min read · 학습
러닝 타입스크립트 CHAPTER 3 - 유니언과 리터럴
타입스크립트에서의 추론
타입스크립트에서는 해당 값을 바탕으로 두 가지 핵심 개념을 통해 추론을 수행합니다.
- 유니언: 값에 허용된 타입을 두 개 이상의 가능한 타입으로 확장합니다.
- 내로잉: 값에 허용된 타입이 하나 이상의 가능한 타입이 되지 않도록 좁힙니다.
타입스크립트에서 코드 정보에 입각한 추론을 해내는 강력한 개념입니다.
유니언 타입
다음 예시를 살펴보겠습니다.
let mathematician = Math.random() > 0.5 ? undefined : 'Mark Goldberg'mathematician 은 undefined 이거나 string 입니다.
이처럼 ‘이거 혹은 저거’와 같은 타입을 유니언이라고 합니다.
유니언 타입은 값이 정확히 어떤 타입인지 모르지만 두 개 이상의 옵션 중 하나라는 것을 알고 있는 경우에 코드를 처리하는 것입니다.
그래서 타입스크립트에서는 mathematician 을 string | undefined 타입으로 간주합니다.
유니언 타입 선언
변수의 초깃값이 있더라도 변수에 대한 명시적 타입 애너테이션을 제공하는 것이 유용할 때도 있습니다. 이때 유니언 타입을 사용할 수 있습니다.
let thinker: string | null = null
if (Math.random() > 0.5) { thinker = 'Susanne Langer' // OK}유니언 타입 선언은 타입 애너테이션으로 타입을 정의하는 모든 곳에 사용 가능합니다.
유니언 속성
값이 유니언 타입일 때 타입스크립트는 유니언으로 선언한 모든 가능한 타입에 존재하는 멤버 속성에만 접근할 수 있습니다.
number | string 타입에서 두 개의 타입에 모두 존재하는 toString() 은 사용할 수 있지만, toUpperCase() 와 toFixed() 는 사용할 수 없습니다.
toUperrCase() 는 number 타입에 없고 toFixed() 는 string 타입에 없기 때문입니다.
모든 유니언 타입에 존재하지 않는 속성에 대한 접근을 제한하는 것은 안전 조치에 해당합니다.
객체가 어떤 속성을 포함한 타입으로 확실하게 알려지지 않은 경우, 타입스크립트는 해당 속성을 사용하려고 시도하는 것이 안전하지 않다고 여기는 것입니다.
유니언 타입으로 정의된 여러 타입 중 하나의 타입으로 된 값의 속성을 사용하려면 코드에서 값이 보다 구체적인 타입 중 하나라는 것을 타입스크립트에 알려야 합니다.
이것을 내로잉이라고 부릅니다.
내로잉
내로잉은 값이 정의, 선언 혹은 이전에 유추된 것보다 더 구체적인 타입임을 코드에서 유추하는 것입니다. 타입스크립트가 값의 타입이 이전에 알려진 것보다 더 좁혀졌다는 것을 알게 되면 값을 더 구체적인 타입으로 취급합니다.
타입을 좁히는 데 사용할 수 있는 논리적 검사를 타입 가드라고 합니다.
흔히 사용하는 타입 가드들을 살펴보겠습니다.
값 할당을 통한 내로잉
변수에 값을 직접 할당하면 타입스크립트는 변수의 타입을 할당된 값의 타입으로 좁힙니다.
let admiral: number | string
admiral = 'Grace Hopper'
admiral.toUpperCase() // OK: string
admiral.toFixed()// Error: Property 'toFixed' does not exist on type 'string'.변수에 유니언 타입 애너테이션이 명시되고 초깃값이 주어질 때 값 할당 내로잉이 작동합니다.
타입스크립트는 변수가 나중에 유니언 타입으로 선언된 타입 중 하나의 값을 받을 수 있지만, 처음에는 초기에 할당된 값의 타입으로 시작합니다.
let inventor: number | string = 'Hedy Lamarr'
inventor.toUpperCase() // OK: string// Error: Property 'toFixed' does not exist on type 'string'.inventor 는 number | string 타입으로 선언되었지만 초깃값으로 문자열이 할당되어 타입스크립트는 즉시 string 타입으로 바로 좁혀지게 됩니다.
조건 검사를 통한 내로잉
일반적으로 타입스크립트에서는 if 문을 통해 변수의 값을 좁히는 방법을 사용합니다. if 문 내에서 변수가 알려진 값과 동일한 타입인지 확인합니다.
// scientist: number | string의 타입let scientist = Math.random() > 0.5 ? 'Rosalind Franklin' : 51
if (scientist === 'Rosalind Franklin') { // scientist: strinig의 타입 scientist.toUpperCase() // OK}
// scientist: number | string의 타입scientist.toUpperCase()// Error: Property 'toUpperCase' dose not exist on type 'string | number'.// Property 'toUpperCase' does not exist on type 'number'.조건부 로직으로 내로잉할 때, 타입스크립트 타입 검사 로직은 훌륭한 자바스크립트 코딩 패턴을 미러링해 구현합니다.
typeof 검사를 통한 내로잉
타입스크립트는 직접 값을 확인해 타입을 좁히기도 하지만, typeof 연산자를 사용할 수도 있습니다.
let researcher = Math.random() > 0.5 ? 'Rosalind Franklin' : 51
if (typeof researcher === 'string') { researcher.toUpperCase() // OK: string}!를 사용한 논리적 부정과 else 문, 삼항 연산자에서도 잘 작동합니다.
if (!(typeof resarcher === 'string')) { researcher.toFixed() // OK: number} else { researcher.toUpperCase() // OK: string}
typeof researcher === 'string' ? researcher.toUpperCase() // OK: string : researcher.toFixed() // OK: number리터럴 타입
리터럴 타입은 좀 더 구체적인 버전의 원시 타입입니다.
const philosopher = 'Hypatia'philosopher 는 string 타입이지만 “Hypatia”라는 특별한 값입니다.
이것이 바로 리터럴 타입의 개념입니다. 원시 타입 값중 어떤 것이 아닌 특정 원싯값으로 알려진 타입이 리터럴 타입입니다.
원시 타입 string은 존재할 수 있는 모든 가능한 문자열의 집합을 나타냅니다. 하지만 리터럴 타입 “Hypatia”는 하나의 문자열만 나타냅니다.
변수를 const로 선언하고 직접 리터럴 값을 할당하면 타입스크립트는 해당 변수를 할당된 리터럴 값으로 유추합니다.
즉, 원시 타입은 해당 타입의 가능한 모든 리터럴 값의 집합입니다.
- boolean: true | false
- null과 undefined: 둘 다 자기 자신. 즉, 오직 하나의 리터럴 값만 가짐
- number: 0 | 1 | 2 … | 0.1 | 0.2 | …
- string: “” | “a” | “b” | “c” | … | “aa” | “ab” | “ac” | …
유니언 타입 애너테이션에서는 리터럴과 원시 타입을 섞어서 사용할 수 있습니다.
let lifespan: number | 'ongoing' | 'uncertain'
lifespan = 89 // OKlifespan = 'ongoing' // OK리터럴 할당 가능성
0과 1처럼 동일한 원시 타입일지라도 서로 다른 리터럴 타입은 서로 할당할 수 없습니다.
let specificallyAda: 'Ada'
specificallyAda = 'Ada' // OK
specificallyAda = 'Byron'// Error: Type "Byron" is not assignable to type '"Ada"'.
let someString = '' // 타입: string
specificallyAda = someString// Error: Typ 'string' is not assignable to type '"Ada"'.specificallyAda 는 리터럴 타입으로 “Ada”가 선언되어 있어 “Byron”이나 string 타입 값은 할당할 수 없습니다.
반대로 리터럴 타입은 그 값이 해당하는 원시 타입에 할당할 수 있습니다. 그래서 someString 변수에 특정 리터럴 문자열이 할당 가능합니다.
someString = ':)'엄격한 null 검사
리터럴로 좁혀진 유니언의 힘은 타입스크립트에서 엄격한 null 검사라 부르는 타입 시스템 영역인 ‘잠재적으로 정의되지 않은 undefined 값’으로 작업할 때 특히 두드러집니다.
십억 달러의 실수
저는 이를 십억 달러의 실수라고 부릅니다. 1965년, null 참조의 발명으로 수많은 오류, 취약성 및 시스템 충돌이 발생했으며 지난 40년 동안 십억 달러의 고통과 피해를 입었을 것입니다.
‘십억 달러의 실수’는 다른 타입이 필요한 위치에서 null 값을 사용하도록 허용하는 많은 타입 시스템을 가리키는 업계 용어입니다. 엄격한 null 검사가 없는 언어에서는 다음 예제 코드처럼 string 타입 변수에 null 을 할당하는 것이 허용됩니다.
const firstName: string = null;
타입스크립트 컴파일러는 strictNullChecks 를 통해 엄격한 null 를 활성화할지 여부를 결정합니다. strictNullChecks 를 비활성화하면 코드의 모든 타입에 | null | undefined 를 추가해야 모든 변수가 null 또는 undefined 를 할당할 수 있습니다.
strictNullChecks 을 false로 설정하면 다음 코드의 타입은 안전하다고 간주하지만 nameMaybe 변수가 .toLowerCase 에 접근할 때 undefined 가 되는 것은 잘못된 것입니다.
let nameMaybe = Math.random() > 0.5 ? 'Tony Hoare' : undefined
nameMaybe.toLowerCase()// Potential runtime error: Cannot read property 'toLowerCase' of undefined.엄격한 null 검사가 활성화되면, 타입스크립트는 다음 코드에서 발생하게 될 잠재적 충돌을 확인합니다.
let nameMaybe = Math.random() > 0.5 ? 'Tony Hoare' : undefined
nameMaybe.toLowerCase()// Error: Object is possibly 'undefined'.엄격한 null 검사를 활성화해야만 코드가 null 또는 undefined 값으로 인한 오류로부터 안전한지 여부를 쉽게 파악할 수 있습니다.
참 검사를 통한 내로잉
타입스크립트는 잠재적인 값 중 truthy로 확인된 일부에 한해서만 변수의 타입을 좁힐 수 있습니다.
let geneticist = Math.random() > 0.5 ? 'Barbara McClintock' : undefined
if (geneticist) { geneticist.toUpperCase() // OK: string}
geneticist.toUpperCase()// Error: Object is possibly 'undefined'.논리 연산자인 && 와 ? 는 참 여부를 검사하는 일도 잘 수행합니다. 하지만 참 여부 확인 외에 다른 기능은 제공하지 않습니다. string | undefined 값에 대해 알고 있는 것이 falsy라면, 그것이 빈 문자열인지 undefined 인지 알 수 없습니다.
geneticist && geneticist.toUpperCase() // OK: string | undefinedgeneticist?.toUpperCase() // OK: string | undefined다음 코드에서 biologist 는 false | string 타입이고, if 문에서 string으로 좁힐 수 있지만, else 문에서 biologist 가 빈 문자열인 경우에는 여전히 string 이 될 수 있음을 알 수 있습니다.
let biologist = Math.random() > 0.5 && 'Rachel Carson'
if (biologist) { biologist // 타입: string} else { biologist // 타입: false | string}초깃값이 없는 변수
자바스크립트에서 초깃값이 없는 변수는 기본적으로 undefined 가 됩니다.
타입스크립트에서 undefined 를 포함하지 않는 타입으로 변수를 선언한 다음, 값을 할당하기 전에 사용하려고 하면 에러가 발생합니다.
let mathematician: string
mathematician?.length// Error: Variable 'mathematician' is used before being assigned.
mathematician = 'Mark Goldberg'mathematician.length // OK타입 별칭
대부분의 유니언 타입은 두세 개의 구성 요소만 갖습니다. 그러나 가끔 반복해서 입력하기 불편한 조금 긴 형태의 유니언 타입을 발견할 수 있습니다.
이때 타입스크립트는 재사용하는 타입에 더 쉬운 이름을 할당하는 타입 별칭이 있습니다.
타입 별칭은 type NEW_NAME = TYPE 형태를 갖습니다. 파스칼 케이스로 이름을 지정합니다.
type RawData = boolean | number | string | null | undefined
let rawDataFirst: RawDatalet rawDataSecond: RawDatalet rawDataThird: RawData타입 별칭은 자바스크립트가 아닙니다
타입 별칭은 자바스크립트로 컴파일되지 않습니다. 순전히 타입스크립트 타입 시스템에만 존재합니다. 런타임 코드에서는 참조할 수 없습니다.
type SomeType = string | undefined
console.log(SomeType)// Error: 'SomeType' only refers to a type, but is being used as a value here.타입 별칭은 순전히 ‘개발 시’에만 존재합니다.
타입 별칭 결합
타입 별칭은 다른 타입 별칭을 참조할 수 있습니다. 사용 순서대로 타입 별칭을 선언할 필요는 없습니다.
type Id = number | string
// IdMaybe 타입은 다음과 같음: number | string | undefined | nulltype IdMaybe = Id | undefined | null마치며
- 유니언 타입으로 두 개 이상의 타입 중 하나일 수 있는 값을 나타내는 방법
- 타입 애너테이션으로 유니언 타입을 명시적으로 표시하는 방법
- 타입 내로잉으로 값의 가능한 타입을 좁히는 방법
- 리터럴 타입의 const 변수와 원시 타입의 let 변수의 차이점
- ‘십억 달러의 실수’와 타입스크립트가 엄격한
null검사를 처리하는 방법 - 존재하지 않을 수 있는 값을 나타내는 명시적인 |
undefined - 할당되지 않은 변수를 위한 암묵적인 |
undefined - 반복적으로 사용하고 입력이 긴 유니언 타입을 타입 별칭에 저장하는 방법