typescript

2.23 ~ 2.27

케케_ 2024. 10. 13. 13:30

목차

 

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. [1,2,3] 배열 타입이 있을 때 -> L은 [1,2] , R은 3
  2. [R,...Reverse<L>] -> [3, ... Reverse<[1,2]>
  3. Reverse<[1,2] -> [2, ... Reverse<[1]>
  4. Reverse<[1] -> [1, ... Reverse<[]>
  5. Reverse<[1] -> []이므로 Reverse<[1]은 [1]
  6. Reverse<[1,2] -> [2,1]
  7. 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가 문자열일 수도 있다고 생각 (아래 사진 참고)

다음과 같이 universe를 추론하고 있음

 

 

(해결) 객체 리터럴 뒤에 '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' 카테고리의 다른 글

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
기본 문법 익히기 (2.7) - 타입스크립트의 타입  (1) 2024.09.20