목차
2.23 타입을 좁혀 정확한 타입을 얻어내자
: 타입 종류가 많기 때문에 타입 구분이 중요!
typeof 연산자
function strOrNum (param : string|number) {
if (typeof param === 'string') {
param; //param: string
} else if (typeof param === 'number'){
param; //param: number
} else {
param; //param: never
}
}
- else로 갈 경우 never가 된다
제어 흐름 분석 : 타입스크립트가 코드르 파악해 타입을 추론하는 것
typeof가 완변한건 X
function strOrNullOrUndefined(param : string|null|undefined) {
if (typeof param === 'undefined'){
param; //param : 'undefined'
} else if (param) {
param; //param : 'string'
} else {
param; //param : 'string|null'
}
}
- else에서 string|null이 됨
- ''(빈 문자열)일 경우 else문에서 string이 될 수 있음
- 자바스크립트에서 typeof null이 object임 (유명 버그)
- 객체와 typeof 결과가 같아 typeof로 null 구분 불가
해결 - null 먼저 거르기 (확인 필요)
function strOrNullOrUndefined(param : string|null|undefined) {
if (param === undefined){
param; //param : 'undefined'
} else if (param === null) {
param; //param : 'null'
} else {
param; //param : 'string'
}
}
명시적으로 유니언인 타입만 타입 좁히기를 할 수 있는건 아님
boolean 타입
function trueOrFalse(param : boolean) {
if (param){
param; //param : 'true'
}else {
param; //param : 'false'
}
}
- boolean은 true 또는 false이기 떄문에 위와 같이 작성 가능
배열 구분하기
function strOrNumArr(param : string | number[]) {
if (Array.isArray(param)){
param; //param : number[]
}else {
param; //param : string
}
}
- Array.isArray 사용
- 조건으로 typeof param === 'string'도 가능
클래스 구분하기 - instanceof 연산자 사용
class A {}
class B {}
function classAorB (param : A | B) {
if (param instanceof A){
param; //param : A
}else {
param; //param : B
}
}
- 함수도 instanceof Function으로 구분 가능
두 객체 구분하기
- X와 Y를 구분하는 코드
interface X {
width : number;
height : number;
}
interface Y {
length : number;
cwnter : number;
}
function objXorY (param : X | Y) {
if (param instanceof X){ // X에 에러 발생/ 타입이 값으로 사용됨
param;
}else {
param;
}
}
- 타입 좁히기는 자바스크립트 문법을 사용해서 진행해야 함
- if문 -> js에서 실행되는 코드
- X -> js 값이 아니라 타입 스크립트의 인터페이스
타입 좁히기는 자바스크립트 문법을 사용해서 진행해야 한다.
자바스크립트에서도 실행할 수 있는 코드여야 하기 떄문이다.
- instanceof를 사용할 수 없다면 X와 Y의 차이에 주목 (속성이 다름)
해결하기 - 1차
interface X {
width : number;
height : number;
}
interface Y {
length : number;
cwnter : number;
}
function objXorY (param : X | Y) {
if (param.width){ // 에러-width 속성은 Y에 존재하지 않음
param;
}else {
param;
}
}
- 위의 방식은 자바스크립트에서 유효한 방식
- 타입스크립트 -> 에러
- 아직 타입 좁히기가 이루어 지지 않은 상황에서 width 속성에 접근했기 때문 (param에 Y가 포함됨)
해결
interface X {
width : number;
height : number;
}
interface Y {
length : number;
cwnter : number;
}
function objXorY (param : X | Y) {
if ('width' in param){
param; //param : X
}else {
param; //param : Y
}
}
- in 연산자 - js에서 사용하는 문법
- js에서는 param.width와 'width' in param 모두 사용 가능
- width 속성 존재
- O -> X타입
- X -> Y타입
(서로 다른 객체가 공동 속성을 가지는 경우)
- 브랜드 속성사용해 객체 구분하기
interface Money {
__type : 'money';
amount : number;
unit : string;
}
interface Liter {
__type : 'liter';
amount : number;
unit : string;
}
function moneyOrLiter (param : Money | Liter) {
if (param.__type === 'money'){
param; //param : Money
}else {
param; //param : Liter
}
}
- in 연산자 대신 바로 속성에 접근 (공통 속성)
직접 타입 좁히기 함수 만들기
interface Money {
__type : 'money';
amount : number;
unit : string;
}
interface Liter {
__type : 'liter';
amount : number;
unit : string;
}
function isMoney (param : Money | Liter) {
if (param.__type === 'money'){
param; //param : Money
}else {
param; //param : Liter
}
}
function moneyOrLiter (param : Money | Liter) {
if (isMoney(param)){
param; //param : Money | Liter
}else {
param; //param : Money | Liter
}
}
- moneyOrLiter에서 타입 구분 못하고 모두 Money | Liter 로 추측
(해결) isMoney 함수에서 특수한 작업해주기
function isMoney (param : Money | Liter): param is Money {
if (param.__type === 'money'){
return true;
}else {
return false;
}
}
function moneyOrLiter (param : Money | Liter) {
if (isMoney(param)){
param; //param : Money
}else {
param; //param : Liter
}
}
- 타입 서술 함수(type predicate) : param is Money 와 같이 반환값 타입을 표기
- predicate(근거를 두다, 단정하다): 매개변수 하나를 받아 boolean을 반환하는 함수
- param is Money 타입 -> 기본적으로 boolean
- is 사용 -> 반환값이 true일 때, 매개변수 타입도 is 뒤에 적은 타입으로 좁혀짐
- (주의) is 연산자 사용시 타입을 잘못 적는 실수하지 않게 조심하기
function isMoney (param : Money | Liter): param is Liter {
if (param.__type === 'money'){
return true;
}else {
return false;
}
}
function moneyOrLiter (param : Money | Liter) {
if (isMoney(param)){
param; //param : Liter
}else {
param; //param : Money
}
}
- 위와 같이 반대로 적지 않게 주의
- 최대한 기본적인 타입 좁히기 사용 -> 안될 경우 타입 서술 사용
2.24 자기 자신을 타입으로 사용하는 재귀 타입이 있다
재귀함수 : 자기 자신을 다시 호출하는 함수
타입스크립트에는 재귀 타입 존재 : 자기 자신을 타입으로 다시 사용하는 타입
type Recursive = {
name : string;
children : Recursive[];
};
const recur1 : Recursive = {
name : 'test',
children : [],
}
const recur2 : Recursive = {
name: 'test',
children: [
{name: 'test2', children:[]},
{name: 'test3', children:[]},
]
}
- Recursive 객체 타입 안 속성의 타입으로 다시 Recursive를 사용
컨디셔널 타입에 사용하기
type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;
타입 인수로는 사용 불가
type T = number | string | Record<string, T>; //에러 발생
(해결) - 타입 인수 안쓰면 됨
type T = number | string | {[key: string] : T};
무한 호출 에러 조심
재귀 타입의 대표 예 - JSON 타입
JSON 타입: 문자열, 숫자, 불 값, null 그 자체거나 다른 json 타입으로 구성된 배열 또는 객체
json 배열 또는 json 갹체 내부에는 다른 json이 들어 있을 수 있어 재귀 타입으로 선언해야함
type JSONType =
| string
| boolean
| number
| null
| JSONType[]
| {[key : string] : JSONType};
const a : JSONType = 'string';
const b : JSONType = [1, false, {'hi' : 'json'}];
const c : JSONType = {
prop : null,
arr : [{}]
}
- 복잡한 구조를 쉽게 표현 가능
배열 타입을 거꾸로 뒤집기
예 ) [1,2,3] -> [3,2,1]
type Reverse<T> = T extends [...infer L, infer R] ? [R,...Reverse<L>] : [];
- [1,2,3] 배열 타입이 있을 때 -> L은 [1,2] , R은 3
- [R,...Reverse<L>] -> [3, ... Reverse<[1,2]>
- Reverse<[1,2] -> [2, ... Reverse<[1]>
- Reverse<[1] -> [1, ... Reverse<[]>
- Reverse<[1] -> []이므로 Reverse<[1]은 [1]
- Reverse<[1,2] -> [2,1]
- Reverse<[1,2,3] -> [3,2,1]
위 Reverse를 이용해 매개변수 순서 바꾸기
type Reverse<T> = T extends [...infer L, infer R] ? [R,...Reverse<L>] : [];
type FlipArguments<T> = T extends (...args : infer A) => infer R
? (...args : Reverse<A>) => R
: never;
type Filpped = FlipArguments<(a : string, b:number , c : boolean) => string>
2.25 정교한 문자열 조작을 위해 템플릿 리터럴 타입을 사용하자
: 특수한 문자열 타입으로 타입을 만들기 위해 사용
: js의 ` 와 ${} 를 사용한 템플릿 리터법과 사용법 비슷
type Literal = 'literal';
type Template = `template ${Literal}`;
const str : Template = `template literal`;
- 문자열 타입 안에 다른 타입을 변수처럼 넣을 수 있음
- 정해진 문자열만 변수에 대입 가능
문자열 변수를 엄격하게 관리 가능
type Template = `template ${string}`;
let str : Template = 'template ';
str = 'template hello'
str = `template 123`;
str = `template`; //(에러) 띄어쓰기 없음
문자열의 조합 표현시 편리
type City = 'seoul' | 'suwon' | 'busan';
type Vehicle = 'car' | 'bike' | 'walk';
type ID = `${City} : ${Vehicle}`;
const id = 'seoul:walk'
- 지역과 수단을 구분해 타입을 지정
- 조합이 편리해짐
제네릭 및 infer와 사용하기
- 'xxtestxx' 앞뒤의 xx를 지우기
type RemoveX<Str> = Str extends `x${infer Rest}`
? RemoveX<Rest>
: Str extends `${infer Rest}x` ? RemoveX<Rest> : Str;
type Removed = RemoveX<'xxtestxx'> //"test"
- 양쪽 공백 지우기
type RemoveEmpty<Str> = Str extends ` ${infer Rest}`
? RemoveEmpty<Rest>
: Str extends `${infer Rest} ` ? RemoveEmpty<Rest> : Str;
type Removed = RemoveEmpty<' test '> //"test"
2.26 추가적인 타입 검사에는 satisfies 연산자를 사용하자
: 타입 추론을 그대로 활용하면서 추가로 타입 검사를 하고 싶을 때 사용
(상황) 객체의 타입을 선언 및 검사 - sirius 대신 sriius 로 오타
const universe = {
sun : "star",
sriius : "star", // sirius 오타
earth : {type : "planet", parent : "sun"}
};
- 속성 키의 타입
- sun | sirius | earth
- 속성 값의 타입
- {type : string, parent : string} | string
-> 인덱스 시그니처 사용해 오타 잡기
const universe : {
[key in 'sun' | 'sirius' |'earth'] : {type : string, parent : string} | string
}= {
sun : "star",
sriius : "star", // sirius 오타 -> 오류 발생으로 확인
earth : {type : "planet", parent : "sun"}
};
속성 값 사용 시 문제 -> earth의 타입이 객체임을 잡지 못함
//(오류)속성 'type'은 '{type : string, parent : string} | string' 그리고 'string'에 존재하지 않는 타입
universe.earth.type
- 인덱스 시그니처에서 속성 값의 타입을 객체와 문자열의 유니언으로 표기 -> earth가 문자열일 수도 있다고 생각 (아래 사진 참고)
(해결) 객체 리터럴 뒤에 'satisfied 타입' 표기
- 문자열, 객체로 정확히 추론하면서 오타를 발견하는 방법
const universe = {
sun : "star",
sriius : "star", //오타로 인한 에러 발생
earth : {type : "planet", parent : "sun"}
} satisfies {
[key in 'sun' | 'sirius' |'earth'] : {type : string, parent : string} | string
};
const universe = {
sun : "star",
sirius : "star",
earth : {type : "planet", parent : "sun"}
} satisfies {
[key in 'sun' | 'sirius' |'earth'] : {type : string, parent : string} | string
};
universe.earth.type //오류 발생 X
- earth의 속성도 사용 가능
2.27 타입스크립트는 건망증이 심하다.
타입을 실수로 주장하는 경우 나타나는 실수
try {} catch (error) {
if (error as Error) {
error.message; //error is of type 'unknown'
}
}
- error가 여전히 unknown이라고 나옴
- as로 강제 주장은 일시적임
- if문 참거짓 판간 후 원래로 돌아감
(해결) 변수 사용
try {} catch (error) {
const err = error as Error
if (err) {
err.message;
}
}
제일 좋은 방법 : as 사용 X
다음와 같이 Error 클래스 인스턴스 이용
try {} catch (error) {
if (error instanceof Error) {
error.message;
}
}
'typescript' 카테고리의 다른 글
lib.es5.d.ts 분석하기 - 3.1 ~ 3.6 (2) | 2024.11.03 |
---|---|
2.28 ~ 2.32 (2) | 2024.10.20 |
2.18 ~ 2.22 (1) | 2024.10.06 |
타입스크립트 2.13 ~ 2.17 (0) | 2024.09.28 |
2.8 ~ 2.12 (별칭, 인터페이스, 객체 속성, 집합, 상속) (0) | 2024.09.20 |