๋ง์ถค ์ ์ฅ TypeScript
- #TypeScript
- #Type Guard
- #static typed language
- #Type Manipulation
- #fromm
๋ค์ด๊ฐ๋ฉฐ
์๋ ํ์ธ์. ๋ ธ๋จธ์ค์์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ํ๊ณ ์๋ ์ ์๊ธฐ์ ๋๋ค.
์ธํฐ๋ท์์ TypeScript์ โType Guardโ์ ๋ํด ๊ฒ์์ ํด๋ณด๋ฉด, ๋ง์ ๊ธ๋ค์ด ๋์ค๊ธด ํ์ง๋ง, ๋ง์ ์ด๋ค ์ํฉ์์ ์ด๋ฅผ ์ ์ฉํ ๋ ์ง๊ฐ๋ฅผ ๋ฐํํ๋์ง์ ๋ํ ์์๊ฐ ๋ถ์กฑํ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋์ ์ด๋ฒ ๊ธ์์๋ ์ค์ ํ๋ก์ ํธ์์ TypeScript์ Type Guard๋ฅผ ์ด๋ป๊ฒ ํ์ฉํ๋์ง์ ๋ํด ์๊ฐํ๊ณ ์ ํฉ๋๋ค.
๊ฐ๋ฐ
ํ๋ก์ ํธ ๋ฐฐ๊ฒฝ
์ ํฌ๋ ์ผ๋ง์ ๊ณ ๊ฐ์ฌ๋ฅผ ์ํ B2B ์๋น์ค์ธ ํํธ๋์ผํฐ๋ฅผ ์คํํ์ต๋๋ค.
๊ณ ๊ฐ์ฌ๋ ํํธ๋์ผํฐ๋ฅผ ํตํด ์ํฐ์คํธ์ ์ฑ๋์ ๊ณต์ง์ฌํญ์ ์์ฑํ๊ณ , Push ์๋ฆผ์ ๋ณด๋ผ ์ ์์ต๋๋ค.
๋ฒก์๋ ๋ก์ง ๊ฐ๋ฐ ์ ์, ํ๋ก ํธ์๋์ API Spec์ ์ ์ํ๊ณ ,
export class CreateNoticeDto {
title: string;
content: string;
...
notification?: {
targets: NoticeNotificationTargetType[];
time: number;
...
};
}
DB ํ ์ด๋ธ ์ค๊ณ๋ฅผ ์งํํ์์ต๋๋ค.
@Entity('notice')
export class Notice {
...
@Column()
status: NoticeStatusType;
@Column()
title: string;
@Column()
content: string;
@Column()
targets?: NoticeNotificationTargetType[];
@Column()
time?: Date;
...
}
ํ๋ก ํธ์๋์์ interface์์ nested object๋ฅผ ์ฌ์ฉํ ์ด์ ๋ notification object ์ ์ฒด๊ฐ ํ์๋ก ํ๊ฑฐ๋, ํ์๋ก ํ์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
ํ์ง๋ง, DB ํ
์ด๋ธ์์๋ flatํ๊ฒ ๋ฐ์ดํฐ๊ฐ ์ ๋ฆฌ๋์ด ๋ค์ด๊ฐ๊ธฐ ๋๋ฌธ์ ๊ฐ column ๋ณ์์ ๋ํ optional์ ๊ฐ๊ฐ ํด์ํด์ค์ผ ํ๋ ๋ถํธํจ์ด ์๊ธธ ์ ์์ต๋๋ค.
(Type Guard์ ๋ํ ๋ด์ฉ์ด๋ฏ๋ก, notification์ ๋ํ ์ ๊ทํ ๊ฐ์ ๊ฒ์ผ๋ก ๋ด์ฉ์ ํ์ฅํ์ง๋ ์๊ฒ ์ต๋๋ค.)
์ด๋ค ๋ฐ์ดํฐ ๋ฉ์ด๋ฆฌ(๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด) ์ ์ฒด๊ฐ optionalํ๊ฒ ํ๋์ ๊ฐ์ด ํ ๋ ๊ฐ ๊ฐ๋ณ ๋ณ์๋ฅผ flatํ๊ฒ ์ ์ฅํ DB ํ ์ด๋ธ์ instance์์๋ ์ด ๋ณ์๋ค์ ๋ง์ง๋๋ง๋ค ์ฒดํฌ(validation)ํด ์ฃผ์ด์ผ ํ ์๋ ์์ต๋๋ค.
Validation์ ์ข ๋ฅ
๋ฒก์๋ ๋ก์ง์ Guard ์ญํ ์ ๊ด์ฌ์ฌ์ Scope์ ๋ฐ๋ผ ๋ค์ํ๊ฒ ์กด์ฌํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
- Client๋ก ๋ถํฐ ์ ๋ ฅ๋๋ ๋น๊ต์ ์ํธ ์์กด์ ์ด์ง ์์ ๊ฐ๋ณ ๋ณ์์ ๋ํ validation์ DTO์์ ์ฒ๋ฆฌ
- ์ฌ์ฉ์ ์ธ์ฆ์ Framework Guard์์ ์ฒ๋ฆฌ
- ์๋น์ค ๋ก์ง์์ ์์ฐจ์ ์ผ๋ก ์งํ๋๋ ๋์ค class-validator ์์ค ์ด์์ ๋ณต์ก์ฑ์ ํต๊ณผํ ์ดํ์ Type Guard ์ฌ์ฉ์ ๊ถํ๊ณ ์ถ์ด ์ด ๊ธ์ ์์ฑํฉ๋๋ค.
Derived Type ์์ฑ
https://www.typescriptlang.org/docs/handbook/utility-types.html
export type PushableNoticeType = Omit<Notice, 'status' | 'targets' | 'time'> & {
status: NoticeStatusType.READY;
targets: NoticeNotificationTargetType[];
time: Date;
};
Type Guard ์ ์ฉ
https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates
๊ณต์ง์ฌํญ์ ๋ฑ๋กํ ๋, Push ์๋ฆผ์ ๋ณด๋ผ ์๋ ์๊ณ , ๋ณด๋ด์ง ์์ ์๋ ์์ต๋๋ค.
์ค๋ช
์ ์ํด nested object์ ํด๋นํ๋ ๋ณ์๋ฅผ ๋ง์ง ์๊ฒ ์ค์ ํ์์ง๋ง, ๋ ๋ง์ ๋ณ์๋ค์ด ์กด์ฌํ๋ค๋ฉด, ๋ฒก์๋ ๋ก์ง์์ ์ ์ ์ง์ ๋ถํ ์ฝ๋๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
ํ์ง๋ง ์์ PushableNoticeType๊ณผ ๊ฐ์ด, Notice์ผ๋ก ๋ถํฐ ํ์๋์ด ๋น๋ฒํ๊ฒ ์ฌ์ฉ๋ ์๋ก์ด Type์ ๋ง๋ค์ด, Type Guard๋ฅผ ์ ์ฉํด ๋ณด๊ฒ ์ต๋๋ค.
isPushableNotice(notice: Notice, now: Date): notice is PushableNoticeType {
const { status, targets, ... } = notice;
if (!status) {
// check status
throw Error('status');
}
if (!targets) {
// check targets
throw Error('targets');
}
// check time
return true;
}
(์ค๋ช ์ ์ํด, ์ค์ ๊ตฌํ์์ ์ฝ๊ฐ ์ถ์ฝ ํ์์ต๋๋ค.)
isPushableNotice ํจ์๊ฐ ์์์ ์ธ๊ธํ class-validator ์์ค ์ด์์ ๋ณต์ก์ฑ์ ์ฒดํฌํ๋ ์ผ์ข ์ validator๋ผ๊ณ ์๊ฐํฉ๋๋ค.
Type Guard๊ฐ ์ ์ฉ๋ Service Logic์ ํ๋ฆ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
async pushNotice(noticeId: number, now: Date) {
const notice = await this.noticeRepository.getNotice(noticeId);
// ์ฌ๊ธฐ๊น์ง๋ notice: Notice
this.isPushableNotice(notice, now);
// ์ฌ๊ธฐ๋ถํฐ๋ notice: PushableNoticeType
await this.sendNoticePush(notice, now);
}
isPushableNotice ํจ์ ์ ํ๋ก ์ฝ๋์์ IDE Tooltip์ ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ด์ฃผ๊ณ ์์ต๋๋ค.
- isPushableNotice ํจ์ ์ด์
- isPushableNotice ํจ์ ์ดํ
Type Guard๋ฅผ ์ฌ์ฉํ์ง ์๋ ์ํฉ์ ์์ํด ๋ณด๋ฉด,
์ด๋ค ์๋ธ ํจ์ ๋ด์์ notice.xxx์ ๋ํ validation์ ์ ํ๋ค๊ณ ํด๋, ๊ทธ ํจ์๋ฅผ ๋ฒ์ด๋ ๊ณณ์์๋ notice.xxx์ ๋ํ validation์ ๋ค์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
notice์ origin type(Notice
)์ ๊ณ์ ๋ฌ๊ณ ๋ค๋๋ฉด์, ๋ฌธ์ ์ํฉ๋ง๋ค IDE์์ ๋นจ๊ฐ ๋ฐ์ค์ด ๊ทธ์ด์ง๊ฑฐ๋, ์๋๋ฉด ๋ฒก์๋ ๋ก์ง ์ฌ๊ธฐ ์ ๊ธฐ์ ๋ฐ๋ณต์ ์ธ validation์ ์ฌ๋ฌ๋ฒ ํ ์๋ ์์ต๋๋ค.
์ด์ ๊น์ง ์ค๋ช ํ ๋ด์ฉ์ ๊ทธ๋ฆผ์ผ๋ก ํํํด ๋ดค์ต๋๋ค.
๋ง์น๋ฉฐ
๊ฐ์ธ์ ์ธ ๊ฒฌํด๋ก TypeScript์ Type Manipulation์ ํตํ Type Narrowing์ ์ ๋ง๋ก ๋งค๋ ฅ์ ์
๋๋ค.
Static Type์ Programming ์์ ์์๋ Dynamic ํ๊ฒ ์ฌ์ฉํ๋ ๋๋์ด๋๊นโฆ
ํ์ฌ ๋ด๋ถ repository์์๋ ๋ค์๊ณผ ๊ฐ์ utilities๋ ๋ง๋ค์ด ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
export type WithRequiredProperty<ObjectType, Key extends keyof ObjectType> = Omit<ObjectType, Key> & {
[Property in Key]-?: ObjectType[Property];
};
TypeScript๋ฅผ ์ฌ์ฉํ์ ๋ค๋ฉด, Type Guard, Type Manipulation์ผ๋ก ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด์ ํ์ ์ ์ต๋ํ ์ขํ ๋ง์ถค์ ์ฅ์ ์ ํ ๋ณด์์ฃ .
๊ฐ์ฌํฉ๋๋ค.