타입스크립트가 값을 바탕으로 추론을 수행하는 두 가지 핵심 개념 “유니언”과 “내로잉”
유니언(union): 값에 허용된 타입을 두 개 이상의 가능한 타입으로 확장하는 것
내로잉(narrowing): 값에 허용된 타입이 하나 이상의 가능한 타입이 되지 않도록 좁히는 것
3.1 유니언 타입
“이거 혹은 저거”와 같은 타입을 유니언 이라고 함
값의 정확한 타입은 모르지만 두 개 이상의 옵션 중 하나임을 알고 있는 경우 코드를 처리함
타입스크립트 구문에서는 값이나 구성 요소 사이에 | 연산자를 사용하여 유니언 타입을 나타냄
// let mathematician: string | undefined
let mathematician = Math.random() > 0.5 ? undefined : "Mark Goldberg";
3.1.1 유니언 타입 선언
변수의 초깃값이 있는 경우에도 변수에 대한 명시적 타입 애너테이션을 제공하는 것이 유용할 때 유니언 타입을 사용함
// 초깃값이 null이지만 잠재적으로 string이 될 수 있음 let thinker: string | null = null; if (Math.random() > 0.5) { thinker = "Susanne Langer"; }
3.1.2 유니언 속성
값이 유니언 타입일 때 타입스크립트는 유니언으로 선언한 모든 가능한 타입에 존재하는 멤버 속성에만 접근할 수 있음. 그렇지 않을 경우 타입 검사 오류가 발생함
예) number | string 타입인 변수가 있을 때 해당 변수에 대해 두 개의 타입에 존재하는 toString()은 사용할 수 있지만, toUpperCase()와 toFixed()는 사용할 수 없음. toUpperCase()는 number 타입에 없고, toFixed()는 string 타입에 없기 때문
이는 안전 조치에 해당하는 것으로, 객체가 어떤 속성을 포함한 타입으로 확실하게 알려지지 않은 경우 해당 속성이 존재하지 않을 수도 있기에 이러한 조치를 함
여러 타입 중 하나에 대한 속성을 사용하려면 코드에서 구체적인 타입을 알려주어야 하며 이를 내로잉이라고 함
3.2 내로잉
값이 이전에 유추된 것보다 더 구체적인 타입임을 코드에서 유추하는 것을 내로잉이라고 함
타입을 좁히는 데 사용할 수 있는 논리적 검사를 타입 가드(type guard)라고 함
3.2.1 값 할당을 통한 내로잉
변수에 값을 직접 할당하면 타입스크립트는 변수의 타입을 할당된 값의 타입으로 좁힘
number | string 타입으로 명시한 변수에 string 값을 할당하면 값 할당 내로잉이 작동함
이후에는 number, string 중 하나의 값을 받을 수 있더라도 처음에는 할당된 타입으로 시작함을 이해함
3.2.2 조건 검사를 통한 내로잉
변수가 알려진 값과 같은지 확인하는 if문을 통해 변수의 값을 좁힐 수 있음
let scientist = Math.random() > 0.5 ? "Rosalind Franklin" : 51;
if (scientist === "Rosalind Franklin") {
scientist.toUpperCase(); // Ok
}
scientist.toUpperCase(); // Error
3.2.3 typeof 검사를 통한 내로잉
typeof 연산자를 사용하여 변수의 값이 특정 타입에 해당하는지 확인할 수 있음
let researcher = Math.random() > 0.5 ? "Rosalind Franklin" : 51;
if (typeof researcher === "string") {
researcher.toUpperCase();
}
typeof researcher === "string" ? researcher.toUpperCase() : researcher.toFixed()
3.3 리터럴 타입
구체적인 버전의 원시 타입인 “리터럴 타입”
원시 타입 값 중 어떤 것이 아닌 특정 원싯값으로 알려진 타입
변수를 const로 선언하고 직접 리터럴 값을 할당할 경우 타입스크립트는 해당 변수를 할당된 리터럴 값으로 유추함
const philosopher = "Hypatia"; // const philosopher: "Hypatia"
각 원시 타입은 해당 타입이 가질 수 있는 가능한 모든 리터럴 값의 전체 조합
- boolean: true | false - null과 undefined: 둘 다 자기 자신, 즉, 오직 하나의 리터럴 값만 가짐 - number: 0 | 1 | 2 | 3 | …… | 0.1 | 0.2 | …. - string: “” | “a” | “b” | “c” | …… | “aa” | “ab” | “ac” | …
3.3.1 리터럴 할당 가능성
0과 1처럼 동일한 원시 타입일지라도 서로 다른 리터럴 타입은 서로 할당할 수 없음
let specificallyAda: "Ada"; specificallyAda = "Ada"; // Ok specificallyAda = "Byron" // Error: Type '"Byron"' is not assignable to type '"Ada"'.
3.4 엄격한 null 검사
타입스크립트는 “십억 달러의 실수(The Billion-Dollar Mistake)”를 바로잡기 위해 엄격한 null 검사(strict null checking)를 허용하며 이는 최신 프로그래밍 언어의 큰 변화 중 하나임
3.4.1 십억 달러의 실수
저는 이를 십억 달러의 실수라고 부릅니다. 1965년, null 참조의 발명으로 수많은 오류, 취약성 및 시스템 충돌이 발생했으며 지난 40년 동안 십억 달러의 고통과 피해를 입었을 것입니다. - 토니 호어(Tonny Hoare), 2009
‘십억 달러의 실수’는 다른 타입이 필요한 위치에서 null 값을 사용하도록 허용하는 많은 타입 시스템을 가리키는 업계 용어임
엄격한 null 검사가 없는 언어에서는 다음과 같이 string 타입 변수에 null을 할당하는 것이 허용됨
const firstName: string = null;
타입스크립트 컴파일러는 실행 방식을 변경할 수 있는 다양한 옵션을 제공함(13장)
가장 유용한 옵션 중 하나인 strictNullChecks는 엄격한 null 검사를 활성화할지 여부를 결정함
let nameMaybe = Math.random() > 0.5 ? "Tony Hoare" : undefined; nameMaybe.toLowerCase(); /* 엄격한 null 검사가 비활성화된 경우 - 오류로 판단하지 않음 Potential runtime error: Cannot read property 'toLowerCase' of undefined. */ /* 엄격한 null 검사가 활성화된 경우 Error: Object is possibly 'undefined'. */
3.4.2 참 검사를 통한 내로잉
타입스크립트는 잠재적인 값 중 truthy로 확인된 일부에 한해서만 변수의 타입을 좁힐 수 있음
다음 코드에서 geneticist는 string | undefined 타입이며 undefined는 항상 falsy이므로 타입스크립트는 if문의 코드 블록에서는 geneticist가 string 타입이 되어야 한다고 추론할 수 있음
let geneticist = Math.random() > 0.5 ? "Barbara McClintock" : undefiend; if (geneticist) { geneticist.toUpperCase(); // Ok: string } geneticist.toUpperCase(); // Error: Object is possibly 'undefined'.
논리 연산자 &&와 ?는 참 여부를 검사하는 일도 잘 수행하지만 참 확인 외에 다른 기능은 제공하지 않음
string | undefined 값에 대해 알고 있는 것이 falsy라면, 그것이 빈 문자열인지 undefined인지는 알 수 없음
geneticist && geneticist.toUpperCase(); // Ok: string | undefiend geneticist?.toUpperCase(); // Ok: string | undefiend
3.4.3 초깃값이 없는 변수
자바스크립트에서 초깃값이 없는 변수는 기본적으로 undefined가 됨
값이 할당되기 전에 속성 중 하나에 접근하려는 것처럼 해당 변수를 사용하려고 시도할 경우 오류 메시지가 나타남
변수 타입에 undefined가 포함되어 있는 경우에는 오류가 보고되지 않음
let mathematician: string; mathematician?.length; // Error: Variable 'mathematician' is used before being assigned. let mathematician: string | undefined; mathematician?.length; // Ok
3.5 타입 별칭
타입스크립트에는 재사용하는 타입에 더 쉬운 이름을 할당하는 타입 별칭(type alias)이 있음
type 새로운 이름 = 타입 형태를 갖음
type RawData = boolean | number | string | null | undefined; let rawDataFirst: RawData; let rawDataSecond: RawData;
3.5.1 타입 별칭은 자바스크립트가 아닙니다
타입 별칭은 애너테이션처럼 자바스크립트로 컴파일되지 않음
위의 코드는 다음과 같이 자바스크립트로 컴파일 됨
let rawDataFirst; let rawDataSecond;
타입 시스템에만 존재하므로 런타임 코드에서는 참조할 수 없음
type SomeType = string | undefined; console.log(SomeType); // Error: 'SomeType' only refers to a type, but is being used as a value here.
3.5.2 타입 별칭 결합
타입 별칭은 다른 타입 별칭을 참조할 수 있음
type Id = number | string; // IdMaybe 타입은 다음과 같음: number | string | undefined | null type IdMaybe = Id | undefined | null;