λ°±μλμ λ°μ΄ν°, νμ μ£Όκ³ λ°κΈ°
- #API
- #React
- #DTO
- #Typescript
μλ νμΈμ. λ Έλ¨Έμ€ νλ‘ νΈμλ ν ν©νΈμ°¬μ λλ€.
μ€λμ κ°λ°νμμ νλ‘ νΈμλνκ³Ό λ°±μλνκ°μ API ν΅μ μ λν΄μ μκ°ν΄λ³΄λ €κ³ ν©λλ€. λ¨μν APIλ₯Ό ν΅ν΄ λ°μ΄ν°λ₯Ό μ£Όκ³ λ°λκ² λΏμ΄ μλλΌ μ΄λ€μμΌλ‘ λ νμ΄ λ Όμλ₯Ό νκ³ , μ ν¬ ν λ΄μμ μ΄λ€μμΌλ‘ νμ μ κ΄λ¦¬νλμ§μ λν μ΄μΌκΈ°λ₯Ό μμ μ½λμ ν¨κ» κ°λ° μ§ν μμλλ‘ μ§νν΄λ³΄λ € ν©λλ€.
1. API μΈν°νμ΄μ€ μμ±
μ°μ νλ‘μ νΈμ κΈ°ν, λμμΈμ λν λ Όμκ° μ’ λ£λ ν API μΈν°νμ΄μ€λ₯Ό μμ±ν©λλ€. μμ± μ£Όμ²΄λ λ°±μλ κ°λ° λ΄λΉμκ° μλ νλ‘ νΈ κ°λ° λ΄λΉμκ° μμ±ν©λλ€.
μ²μμλ λ°±μλνμμ μΈν°νμ΄μ€ μμ±κΉμ§ λ΄λΉνμλλ°μ, λ§μ κ°λ°μ΄ μμνκ³ λ³΄λ©΄ νλ‘ νΈνμμλ§ μΈμ§ν μ μλ λ°μ΄ν°λ€μ΄ μμ΄ ν΄λΉ μ λ³΄κ° λλ½λκ³ κ°λ° μ€μ λ³κ²½μ΄ λ°μνλ μΌμ΄ μ¦μμ΅λλ€.
μ΄νλ‘λ νλ‘ νΈμλ νμμ μΈν°νμ΄μ€ μμ±μ ν΅ν΄ νμν λ°μ΄ν°μ λλ½μ μ΅μν νκ³ , λ°±μλμμ νμλ₯Ό ν΅ν΄ λ°±μλ ν λ΄μμ κ΄λ¦¬νλ λ€μ΄λ°μ΄λ 컨벀μ μ μ μ©μμΌ μ΅μ’ μ μΌλ‘ μΈν°νμ΄μ€κ° μμ±λκ³ νλ‘ νΈ νμμλ λ°±μλμ API μμ±μ κΈ°λ€λ¦¬μ§ μκ³ κ°λ°μ μμνκ² λ©λλ€.
μλλ μ ν¬κ° κ°λ°μ μ¬μ©λμλ μΈν°νμ΄μ€μ μΌλΆμΈλ°μ, μ²μμ artiμ κ°μ κ°μ²΄κ° μλλΌ artiId, artiNameμ΄λΌλ κ°κ°μ ν€λ‘ μμ±λμμ§λ§ λ Όμ κ³Όμ μμ artiλΌλ κ°μ²΄μμμ λ¬Άμ΄ μ¬μ©νλκ±Έλ‘ λ Όμλμ΄ λ³κ²½λμμ΅λλ€.
// GET /post/:postId
{
id: number;
type?: 'image' | 'video';
arti: {
id: string;
name: string;
}
}
2. Mock λ°μ΄ν°λ₯Ό ν΅ν κ°λ°
ν΄λΉ μΈν°νμ΄μ€κ° μ ν΄μ§κ³ λλ©΄ νλ‘ νΈμλ νμ ν΄λΉ μΈν°νμ΄μ€λ₯Ό κΈ°λ°μΌλ‘ νλ mock λ°μ΄ν°λ₯Ό μμ±νκ³ κ°λ°μ μμν©λλ€. μ΄λ, ν΄λΉ κ°λ° λ°©μμ λͺ©νλ μ΅μ’ API μ°λμ λ³λμ μμ μμ΄ API μλν¬μΈνΈμ νμ μ μ μν΄μ£Όλ μμ λ§μΌλ‘ μ°λ μλ£λλκ²μ λͺ©νλ‘ ν©λλ€. μ€μ μ½λλ₯Ό λ¨Όμ νλ² λ΄λ³ΌκΉμ?
// post.api.ts
export type GetPostResponse = {
id: number;
type?: 'image' | 'video';
arti: {
id: string;
name: string;
}
}
const getPost = ({postId}: {postId: number}) => {
return fetch<GetPostResponse>({ baseUrl: `/post/${postId}`});
}
// post.vo.ts
export class PostVo {
id: number;
type: 'text' | 'image' | 'video';
arti: {
id: string;
name: string;
}
static from(response: GetPostResponse) {
return new PostVo(response);
}
constructor(response: GetPostResponse) {
this.id = response.id;
this.type = response.type ?? 'text';
this.arti = response.arti;
}
}
// post.query.ts
const useGetPost = (postId: number) => {
return useQuery({
queryKey: ['getPost'],
queryFn: () => {
getPost({postId}).then(PostVo.from)
}
})
}
μ΄λ¬ν νλ¦μ μ½λλ₯Ό μ ν¬λ μμ±νκ³ μλλ°, api.tsλ ν΄λΉ apiμ νΈμΆμ μν μ½λμ΄κ³ query.tsλ react-queryλ₯Ό μ¬μ©νλ λΆλΆμΌλ‘
λλ¦ μ΅μν μ½λλΌκ³ μκ°νμ§λ§ vo.ts νμΌμ μ‘°κΈ μμν κ°λ
μ΄λΌκ³ μκ°ν©λλ€.
μ ν¬κ° μ¬μ©νλ vo.ts νμΌμ DBμ μ΄λμ λ μμ‘΄μ μΈ APIμ λ¦¬ν΄ νμ
μ νλ‘ νΈμμ λ μ
λ§μ λ§μΆ° μ¬μ©νκΈ° μν 컨λ²ν° κ°λ
μΌλ‘ μ΄ν΄ν΄μ£Όμλ©΄ μ’μκ² κ°μ΅λλ€.
μλ₯Ό λ€μ΄, μμμ μΈν°νμ΄μ€λ₯Ό μ μν λ typeμ΄λΌλ ν€μ νμ
μ image
, video
λκ°μ§ νμλ§ κ°μ§μ μλλ° voμμλ ν΄λΉ κ°μ΄ μμλ text
λΌλ κ°μΌλ‘ λ³κ²½νμ¬ μ¬μ©νκ³ μλκ±Έ νμΈν μ μμ΅λλ€.
μ΄λ μ¬μ€ typeμ΄ undefined μΌλ text
νμμ λ°μ΄ν°κ° λμ΄μ€κΈ°λ‘ λμ΄ μμκ³ νλ‘ νΈμμ κ°λ
μ±μ λ μ±κΈ°κΈ° μν΄μ ν΄λΉ λ°μ΄ν°λ₯Ό νλ² λ κ°κ³΅ν΄μ£Όκ³ μλ κ²μ
λλ€.
λλΆμ μ ν¬λ μλμ²λΌ κ°λ
μ±μ΄ ν₯μλ κ°λ°μ΄ κ°λ₯ν΄μ‘μ΅λλ€.
μ typeμ΄ μλλ° text μ°κ΄ λ‘μ§μ΄ λμ€λμ?
κ° μλλΌ typeμ λ§λ text μ°κ΄ λ‘μ§μ΄ λμ€λꡬλ!
λΌκ³ μκ°ν μ μμ΄μ§κ²μ
λλ€.
// X
if (!type) {
// text μ°κ΄ λ‘μ§ λ° UI
}
// O
if (type === 'text') {
// text μ°κ΄ λ‘μ§ λ° UI
}
3. μ€ APIμμ μ°λ
μ ν¬κ° 2λ²μμ μ€λͺ λλ¦° λ°©μμΌλ‘ κ°λ°μ μ§ννκ³ μμΌλ©΄ λ°±μλνμμ κ°λ° μλ£μ λν μλ¦Όμ΄ μ΅λλ€.
μ.. κ·Έλ°λ° μ΄μ°¨νΌ λ°°ν¬νμΌλ©΄, ν΄λΉ μΈν°νμ΄μ€ κΈ°μ€μΌλ‘ μμ±μ΄ λμμν
κ³ νλ‘ νΈμμ ν΄λΉ μ 보 κΈ°μ€μΌλ‘ νΈμΆνλ©΄ λλ μΌμΈλ°
μ λ°°ν¬ λ²μ κΉμ§ 곡μ λ₯Ό ν΄μ£Όμλ κ±ΈκΉμ?
그건 λ°λ‘ λ°±μλμμ ν΄λΉ publishλ₯Ό ν΅ν΄μ APIμ νμ
μ κ°μ΄ λ겨주기 λλ¬Έμ
λλ€.
μ ν¬ λ°±μλμμλ nestjs + typescriptλ₯Ό μ΄μ©νμ¬ κ°λ°μ μ§ννκ³ μλλ° νλ‘ νΈμ κ°μ΄ νμ μ€ν¬λ¦½νΈλ₯Ό μ¬μ©νκ³ μλ€λ³΄λ, λ°±μλκ° μ μν νμ μ λ°°ν¬νκ³ μ ν¬μκ² κ³΅μ ν΄μ£Όλκ±Έλ‘ μ ν¬λ κ·Έ νμ μ κ°λ¨ν λ°μμ μ¬μ©ν μ μκ² λ©λλ€.
κ°λ¨ν μμμ μ μνλ api.ts νμΌμ΄ μλμ²λΌ λ°λκ² λ©λλ€.
// post.api.ts
// before
export type GetPostResponse = {
id: number;
type?: 'image' | 'video';
arti: {
id: string;
name: string;
}
}
const getPost = ({postId}: {postId: number}) => {
return fetch<GetPostResponse>({ baseUrl: `/post/${postId}`});
}
// after
import {GetPostResponse} from '@knowmerce/fromm-channel-post';
const getPost = ({postId}: {postId: number}) => {
return fetch<GetPostResponse>({ baseUrl: `/post/${postId}`});
}
ν΄λΉ λ°©μμ μ¬μ©νμλ λκ»΄μ§λ μ₯μ μΌλ‘λ
- λ°±μλκ° μ λ¬ν΄μ£Όλ νμ μ μ¬μ©νκΈ° λλ¬Έμ ν΄λ¨Όμλ¬λ‘ μΈν νμ μ€μ°¨κ° λ°μνμ§ μμ΅λλ€.
- λ°±μλκ° λ°°ν¬μ μ λ¬ν΄μ£Όλ λ²μ μ ν΅ν΄ νμ μ μ¬μ©νκΈ° λλ¬Έμ λ°±μλ λ΄λΆμμ λ²μ μ΄λ νμ μ λ³κ²½μ μν₯μ λ°μ§μμ΅λλ€.
- ν΄λΉ λ²μ μμ λ³κ²½λ νμ μ΄ μλ€λ©΄ μ΄λμ μλ¬κ° λ°μνκ³ μλμ§ λ°λ‘λ°λ‘ μΈμ§ν μ μμ΄ λ³κ²½λ λΆλΆμ λΉ λ₯΄κ² μ μ©ν μ μμ΅λλ€.
λ§λ¬΄λ¦¬
μ΄μ²λΌ μ ν¬ νμ νλ‘ νΈμλμ λ°±μλ κ°μ ν¨μ¨μ μΈ νμ κ³Ό κ°λ° μλ ν₯μμ μν΄ μΈν°νμ΄μ€ μμ±λΆν° Mock λ°μ΄ν° νμ©, VO ν¨ν΄ λμ , κ·Έλ¦¬κ³ νμ μ€ν¬λ¦½νΈ κΈ°λ°μ νμ 곡μ κΉμ§ μΌλ ¨μ νλ‘μΈμ€λ₯Ό 체κ³μ μΌλ‘ κ΄λ¦¬νκ³ μμ΅λλ€.
μ΄λ¬ν λ°©μμ λ°μ΄ν° λλ½μ΄λ νμ λΆμΌμΉλ‘ μΈν μ€λ₯λ₯Ό μ΅μννκ³ , κ°λ° μ΄κΈ° λ¨κ³λΆν° λͺ νν κΈ°μ€μ μΈμ ν¨μ¨μ μΈ νμ νκ²½μ μ‘°μ±νλ λ° ν° λμμ μ£Όκ³ μμ΅λλ€.
μ΄ κΈμ΄ μ¬λ¬λΆμ νμ κ³Ό API ν΅μ λ° νμ κ΄λ¦¬μ μ°Έκ³ κ° λκΈ°λ₯Ό λ°λλλ€!