본문으로 건너뛰기

grammer

Typescript Cheatsheet

저도 타입스크립트 입문시에 타입에 많이 데였던거같아요 개뽝이였음

블로그에서도 아래처럼 뭔가 다양하게 타입에 대해 적어줬는데, 그래서 뭐가 정답인데? 라고 생각했었는데 정답보다는, 예뻐보이는것을 찾으시면 그게 정답이라고 보셔도 좋아여

시간 지나면, 지금 예뻐보이는게 나중에 안예뻐보일수도있고, 성능상의 이점을 따지려 할수도 있고 Nested 된걸 좋아할수도 있으니 취향 맞는걸로 시작하는게 정신건강에 좋아여

Export / Export default 언제 사용해야하는지?

  • export?

    • 다른 파일에서 이 함수 / 타입 / 변수 등을 사용 하고 싶을때 export 를 붙여주게됨
  • export default?

    • import EEE from 'file.ts', import { EEE } from 'file.ts' 이 두 코드의 차이는 를 붙이냐 안붙이냐인데, export default 를 붙인게 있을때만, 를 뺄수있음
  • 차이? callee.ts (주로 호출 받는 쪽이라 불려요)

    const WHO_AM_I = 'default_var';
    export const XXX = 'im_not_default';
    export default WHO_AM_I;

    caller.ts (호출하는 쪽)

    import XXX from 'callee.ts';

    console.log(XXX);
    -> 결과값은 'default_var' 이 나옴

    caller2.ts (호출22)

    import { XXX } from 'callee.ts';
    console.log(XXX)
    -> 결과값은 'im_not_default' 가 나옴

언제 / 어디에 export 라는걸 붙여야되냐!?

-> 언제: caller.ts 처럼, 다른 파일(호출하는쪽)에서 현재 파일(호출받는쪽)에 있는 변수 / 클래스 / 타입 등을 사용하고싶다면,

어디에: (호출받는쪽)에 type, interface, class, const, let, var 등 변수 / 타입 / 클래스 / 인터페이스 앞에 export 를 붙여줘야함


Type 관련

type JSONResponse = {
version: number; // Primitive Field
/** In Bytes */ // 필드 설명 작성 가능
payloadSize: number; // Primitive 필드
outOfStock?: boolean; // Optional 필드
update: (retryTimes: number) -=> void; // 화살표 함수
update(retryTimes: number): void; // 함수
(): JSONResponse // JSONResponse() 이렇게 호출 가능
[key: string]: number; // any index 허락
new (s: string): JSONSResponse; // Newable
readonly body: string; // 읽기전용 요소
}

예시 1) 호출과 반환값

const requestBody = {
username: 'testuser',
password: 'testpassword',
type: 'user', // 타입은 'user', 'admin', 'superadmin' 이 존재함
}


const responseBody = {
data: {
'access_token': 'asdjfklajsldkfjklasjdf.skdfsdf.asdfsdf',
}, // 만약, 에러일경우, data: null 로 응답
msg: '로그인 되었습니다.',
cd: 202, // cd 는 20X, 30X, 40X, 50X가 존재함
// err: '에러시 에러메세지 반환' 혹은 ['에러메세지1','에러메세지2']
// cd값이 40X, 50X 일 경우에는 err 필드도 존재하며 문자열 혹은 문자열 배열이 가능
}

위와같은 호출 < - > 응답 값이 있을때, 이를 타입 / 인터페이스로 표현한다면 (사실 위에서 아래 순서대로 점차 취향 변화함)

  • Type 표현 ( Nested )

    type RequestBody = {
    username: string;
    password: string;
    type: "user" | "admin" | "superadmin"; // 문자열을 직접 타입으로 지정하여, 그 외 값이 오는것을 사전 방지함
    }

    type ResponseBody = {
    data: {
    access_token: string;
    } | null;
    // 정상응답시 ResponseData 의 타입을, 에러의 경우 null 의 타입을 받음
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • Type 표현 ( Non-Nested )

    type RequestUserType = "user" | "admin" | "superadmin"; // 문자열을 직접 타입으로 지정하여, 그 외 값이 오는것을 사전 방지함
    type RequestBody = {
    username: string;
    password: string;
    type: RequestUserType
    }

    type ResponseData = {
    "access_token": string;
    }

    type ResponseBody = {
    data: ResponseData | null; // 정상응답시 ResponseData 의 타입을, 에러의 경우 null 의 타입을 받음
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • Interface 표현 ( Nested )

    interface RequestBody {
    username: strign;
    password: string;
    type: "user" | "admin" | "superadmin";
    }

    interface ResponseBody {
    data: {
    access_token: string;
    } | null;
    // 정상응답시 ResponseData 의 타입을, 에러의 경우 null 의 타입을 받음
    msg: string;
    cd: number;
    err?: string | string[];
    }

  • 혼종 (Non-Nested)

    type RequestUserType = "user" | "admin" | "superadmin"; // type 부분만 분리해서 사용 할 수도 있음

    interface RequestBody = {
    username: string;
    password: string;
    type: RequestUserType;
    }

    type ResponseData = {
    "access_token": string;
    }

    interface ResponseBody = {
    data: ResponseData | null; // 정상응답시 ResponseData 의 타입을, 에러의 경우 null 의 타입을 받음
    msg: string;
    cd: number;
    err?: string | string[];
    }

API 호출 응답을 정규화시키기

서버측, 응답 정규화를 위한 코드 ( 참고 )

import { HttpStatus } from "@nestjs/common";

export { HttpStatus };

export type RecordTypeReturn = RetType<Record<string, any>>;

interface ReturnType {
cd: number;
data?: T;
err?: string;
msg?: string;
ext?: object;
}

// 응답 공용 구조체
export class RetType<T> extends ReturnType {
// * ... 구현부 생략 * //
getBody(): object {
return {
...this.data,
msg: this.msg,
ext: this.ext,
};
}
}


// 에러 interceptor

if (err instanceof HTTPErr) { // err가 HTTPErr의 인스턴스 일 경우
if (err instanceof RetType) // err가 RetType<any> 의 인스턴스 일경우
{
err = res as string;
msg = (res as string) !== exception.message ? exception.message : '';
ext = {};
} else if (exception instanceof KError) { // KError 의 인스턴스 일 경우
err = isString(res) ? res : res.error || exception.message || '';
msg = isString(res) ? res : res.message || exception.message || '';
ext = exception.getExtraInfo();
} else {
err = exception.message || 'Unknown Error';
msg = res.message || '';
ext = {};
}
} else {
Logger.error(`[GlobalExceptionFilter] - HttpException 이외 에러 발생`);
Logger.error(exception);
err = 'Unknown Error';
msg = "I'm a Tea Pot. Look at Console";
ext = {};
}
// msg: string 으로 변환 시도, string | string[] 으로 봔한됨
// err: string 으로 변환 시도, 대체로 string 으로 반환됨

const exceptionExtraInfo: object = Object.assign(ext, {
timestamp: convertPrettyKST(new Date()),
path: new URL(request.url, `http://${request.headers.host}`).pathname,
});
// ext 정보 + { timestamp: 현재시각, path: 요청url }

const responseBody: RetType<RecordTypeReturn> =
new RetType<RecordTypeReturn>()
.setCode(status)
.setErr(err)
.setMsg(msg)
.setExt(exceptionExtraInfo);

위 축약된 코드들을 슥 샥 봐보면

type GenericResponse = {
err?: string;
msg?: string | string[];
ext?: {
timestamp?: string | number;
path?: string;
},
}
// 이와같이, 공통된 부분만 묶어버릴수 있다

이를 만약 { err: '에러가 발생하였습니다.', xxx: '안된다' } 라는 메세지에 사용하고 싶다면

type ExampleOneResponse = GenericResponse & { xxx: string };
// GenericResponse 타입에 { xxx: string } 을 붙여서 사용하는 방법

// 혹은

interface IExampleOneResponse extends GenericResponse {
xxx: string;
}

// 이렇게도 사용가능함