post-mobism(ํฌ์คํธ ๋ชจ๋น์ฆ, ์ดํ ๋ชจ๋น์ฆ)์ mobithon API๋ฅผ ํ์ฉํด ๋ง๋ ๊ธฐ๋ณธ CRUD ๊ธฐ๋ฅ ๊ตฌํ์ ์ถฉ์คํ ๋ฏธ๋ ํ ์ด ํ๋ก์ ํธ์ ๋๋ค.
์ค ์ฐํด 3์ผ์ ํฌํจํด ์ฝ 12์ผ๋์ ์งํํ ํ๋ก์ ํธ post-mobism์ ๋ชฉํ๋ ์๋ ๋ ๊ฐ์ง์์ต๋๋ค.
์ด์ ํ๋ก์ ํธ Chap-Chap์์ ๋ถ์กฑํ๋ ๋ถ๋ถ์ ๋ฐฑ์๋ API์ ๋ถ์ ๋ถ์กฑ์ด์์ต๋๋ค.
๋ฐฑ์๋ API ๋ถ์์ ์ ๋๋ก ํ์ง ์๊ณ ๋ค์ด๊ฐ ๋น์ mobithon API๋ฅผ ํ์ฉํ์ง ๋ชปํ๊ณ
ํ๋ก ํธ ๋จ ๊ฐ๋ฐ์ ์งํํ๋ฉด์ ๋ก์ง ์์ ์ ๊ต์ฅํ ์์ฃผํด์ผ ํ์ต๋๋ค.
๊ทธ๋์ ์ด๋ฒ์๋ ๊ฐ์ด API๋ฅผ ๋ถ์ํด๋ณด๊ณ ๋ถ์ํ ๋ด์ฉ์ ํ ๋๋ก ๋์์ธ์ ์งํํด๋ณด๊ธฐ๋ก ํ์ต๋๋ค.
API ๋ถ์์ sequence diagram์ ํตํด ์งํํ์ต๋๋ค.
๊ฐ์ด ์งํํ ๋ถ๋ถ์ gitHub์ readme ๋งํฌ๋ค์ด ํ์ผ์ ์ฒจ๋ถ๋์ด ์์ต๋๋ค.
์ฒ์์ ํ์ด์ง ๋ณ๋ก ํ๋ก ํธ ๋จ์ ๋๋์ด ์ด 5๋จ๊ณ(ํด๋ผ์ด์ธํธ-ํ๋ก ํธ(3ํ์ด์ง)-๋ฐฑ์๋)๋ก ๋๋์ด ๋ถ์์ ์งํํ์ผ๋ ํ๋ก์ ํธ์ ํฌ๊ธฐ๊ฐ ์๊ธฐ ๋๋ฌธ์ ๋ผ์ดํธํ๊ฒ ์งํํ๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค๋ ํผ๋๋ฐฑ์ ๋ฐ๊ณ ์์ ๊ฐ์ด ๊ฐ์ํํด ๋ค์ด์ด๊ทธ๋จ์ ๊ทธ๋ ธ์ต๋๋ค.
์ฒ์์ ์์
์์๋ฅผ ์๋ชป ์ง์ ์ด์ง ๋๋ ์ด๊ฐ ๋์ต๋๋ค.
์ค๊ณ โ ์์ดํ๋ ์ โ ๋์์ธ โ ๊ฐ๋ฐ
์์ผ๋ก ์งํํ๊ธฐ ์ํด ๊ฐ์ sequence diagram์ ๋ํด ์์๋ณด๊ณ ๊ทธ๋ ค๋ณด๋ ์๊ฐ์ ๊ฐ๊ธฐ๋ก ํ์ผ๋ ์ค๊ณ๋ฅผ ์งํํ๋ค๋ณด๋ ๋์์ธ์ด ์์ด ์งํํ๊ธฐ๊ฐ ์ ๋งคํ ๋ถ๋ถ์ด ์์์ต๋๋ค.
๋์์ธ์ ๊ด๋ จ๋ ์๊ธฐ๋ฅผ ์ฌ์ ์ ํ์ง ์์ ๋ค์ ์์ด์ดํ๋ ์ โ ์ค๊ณ โ ๋์์ธ โ ๊ฐ๋ฐ
์์ผ๋ก ๊ฐ์ด ์งํํ์ต๋๋ค.
post-mobism์ post-modernism(ํฌ์คํธ ๋ชจ๋๋์ฆ)์์ ์ฐฉ์ํ ์ด๋ฆ์ ๋๋ค.
๋ณธ ํ๋ก์ ํธ์ ์ฃผ ๊ธฐ๋ฅ์ด post์ comment์ CRUD์ด๊ธฐ ๋๋ฌธ์ 'post'๋ผ๋ ํค์๋๋ฅผ ๋ฃ๊ณ ์ถ์์ต๋๋ค. ๋ฐ๋ก post-modernism(ํฌ์คํธ ๋ชจ๋๋์ฆ)์ด ๋ ์ฌ๋ผ mobi์ฃผ์(mobi์ฌ์)์ ๋ง๊ฒ code๋ฅผ postํ๋ค๋ ์ฃผ์ ๋ก ์ด๋ฆ์ ๋ถ์ฌ ํ๋ก์ ํธ๋ฅผ ์งํํ๊ฒ ๋์์ต๋๋ค.
post-mobism์ mobi ์์น๋ค์ ์ฝ๋๋ฅผ ๊ณต์ ํ๊ณ ์๋ก ๋ฆฌ๋ทฐํด์ฃผ๋ ์ฌ์ดํธ์ ๋๋ค.
| "Less is Bore"
์๋๋ ์์ด์ดํ๋ ์์ ์ผ๋ถ์
๋๋ค.
์ฌํด์ ์ปฌ๋ฌ์ธ peach fuzz(ํผ์นํผ์ฆ)๋ฅผ ๋ฒ ์ด์ค ์ปฌ๋ฌ๋ก ์ฌ์ฉ, ํฌ์ธํธ ์ปฌ๋ฌ๋ ์ด์ ์ ์ด์ธ๋ฆฌ๋ ๋ฐ์ ๋ ๋ ์ปฌ๋ฌ๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด...
์ด๋ฒ ํ ์ด ํ๋ก์ ํธ์์ API ๋ถ์์ ์ ๋๋ก ์ด๋ค์ง์ง ์์์ต๋๋ค. ๐ฅฒ
๋ถ์๊ณผ ์ค๊ณ ๊ณผ์ ์์ ์ด๋ค ์๊ธฐ๋ค์ ํ์ด์ผ ํ๋์ง, ๋ถ์์ ํ ๋๋ก ์ด๋ค ๋ถ๋ถ๋ค์ ์ฌ์ ์ ์๋ ผํ์ด์ผ ๊ฐ๋ฐ์ ๋ ํจ์จ์ ์ผ๋ก ํ ์ ์๋์ง๋ฅผ ์ ๋๋ก ๋ชจ๋ฅด๊ณ ์งํํ๊ธฐ ๋๋ฌธ์ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์๋ ๋ง์ด ํค๋งธ์ต๋๋ค.
์๋๋ mobism์ ์งํํ๋ ๊ณผ์ ์์ ๊ฒช์ ์ํ์ฐฉ์ค๋ฅผ ํ ๋๋ก ์์ผ๋ก ์ด๋ป๊ฒ ์งํํ๋ฉด ์ข์์ง ๋ค์ ํ ๋ฒ ๋ ์๊ฐํด๋ณธ ๋ค ์์ฑํด๋ดค์ต๋๋ค.
ํ์
์ ์ํด ์ฌ์ฉ ์์ ์ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ์
ํด์ ๊น์์ค๋๋ค.
๊ธฐ๋ณธ ์ธํ
์ ํ๋ก์ ํธ ํด๋ ์์ฑ ํ API ๊ด๋ จ ๋ก์ง ์์ฑ ์ ์ ์งํํ๋ฉด ๋ฉ๋๋ค.
sequence diagram์ผ๋ก API ๋ฌธ์์ wire-frame์ ํ ๋๋ก
๊ธฐ๋ฅ์ ๊ตฌํ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ๋ฐ์ ธ๋ด
๋๋ค.
BE ๊ฐ๋ฐ์๋ก๋ถํฐ ์ ๋ฌ๋ฐ์ API ์๋ด์(?)๋ฅผ ํ ๋๋ก ์ฌ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๋ณด๋ด๋ด
๋๋ค.
ํ์ธ ๋ฐ ์๋ต์ผ๋ก ์ด๋ค ํํ์ ๋ฐ์ดํฐ๊ฐ ์ค๋์ง ํ์ธํ๊ธฐ ์ํด ๊ผญ ์งํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
์ฌ๋ ํด๋ผ์ด์ธํธ๋ก ์์ฒญ์ ๋ณด๋ด๋ณด๋ฉด์ ์ด๋ค ๊ฐ์ ํ์๋ก ํ๋์ง, ์ด๋ป๊ฒ ๋ณด๋ผ์ง ๋ฑ์ ํ์คํ ํ ์ ์์ต๋๋ค. ์ถ๊ฐ์ ์ผ๋ก ์์ฒญ ์งํ ์ค ์ค๋ฅ๋ฅผ ๋ฐ๊ฒฌํ ๊ฒฝ์ฐ, BE ๊ฐ๋ฐ์์๊ฒ ๋น ๋ฅธ notice๋ฅผ ํ ์ ์์ด ํจ์จ์ ์ธ ๊ฐ๋ฐ์ ์งํํ ์ ์์ต๋๋ค.
sign-up & sign-in
get & post post data
`post comment`
์์์ thunder client๋ก ํ์ธํ ๋ฐ์ดํฐ์ ํํ๋ฅผ ๊ทธ๋๋ก ๊ฐ์ ธ์ ๋ชฉ๋ฐ์ดํฐ์ type์ ๋ง๋ญ๋๋ค.
form type
export type SignUpUser = {
userId: string
password: string
nickname: string
}
export type SignInUser = {
userId: string
password: string
nickName: string
profileUrl: string
token?: string
}
export type User = {
id: string
nickName: string
profileImg: string
}
post type
export type TPostsResponse = {
data: Post[] | []
pageNation?: Pagination
}
export type Post = {
id: string
data: { title: string; content: string }
dataUser: { data: { nickName: string }; profile_url: string; userId: string }
dataImage?: []
createdAt: string
}
export type Pagination = {
start: number
end: number
total: number
set: number
current: number
}
comment type
export type TCommentsResponse = {
data: Comment[]
pageNation?: Pagination
}
export type Comment = {
id: string
data: { content: string }
dataUser: { data: { nickName: string }; profile_url: string; userId: string }
createdAt: string
}
๋จผ์ ๊ฒน์น๋ ๋ก์ง์ core.ts์ ๋ฐ๋ก ๋ถ๋ฆฌํด ํ์ผ์ ์์ฑํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฐ๊ฐ ๊ธฐ๋ฅ๋ณ๋ก ํ์ผ์ ๋๋ ์ ๋ฆฌํฉ๋๋ค.
features/core.ts
const token = localStorage.getItem(ACCESS_TOKEN)
export const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_BACKEND_URL,
headers: {
authorization: token ? `Bearer ${token}` : null,
},
params: {
apiKey: import.meta.env.VITE_API_KEY,
pair: import.meta.env.VITE_PAIR,
},
withCredentials: true,
})
// and others ...
features/user/auth.api.ts
const PATH = "/user"
export const AuthApi = {
// sign-up
// sign-in
// sign-out
// refresh
}
async SignIn(data: SignInType) {
const res = await axiosInstance.post(PATH + "/sign-in", data)
const userInfo = {
userId: res.data.userId,
nickName: res.data.info.nickName,
profileUrl: res.data.info?.profileUrl,
}
localStorage.setItem("userInfo", JSON.stringify(userInfo))
return res
},
}
// and others ...
features/post/post.api.ts
const POST_PATH = "/data/post"
export const PostApi = {
// getPost
// postPost
// editPost
// deletePost
}
/**
* @function getPost
* @method GET
* @params dataName: string
* @queries parentId: string, page: number, limit: boolean
*/
async getPosts(pageParam: number) {
const res = await axiosInstance.get<TPostsResponse>(POST_PATH, {
params: {
page: pageParam,
},
})
console.log("getPosts", res.data)
return res.data
},
// and others ...
๋ณธ ํ๋ก์ ํธ๋ ์ ์ญ ์ํ๋ ๋น๊ต์ ์์ฃผ ์ฌ์ฉํ๋ recoil, ๋น์ทํ jotai ๋ฑ์ ๋ค๋ก ํ๊ณ ์ฒ์ ์ฌ์ฉํด๋ณด๋ Redux Toolkit์ผ๋ก ๊ด๋ฆฌํ๊ณ ์์ต๋๋ค.
ํ์ฌ ์ธ๊ธฐ ์๋ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์๋์ง๋ง ์ด์ ๋ถํฐ ํ์ฌ๊น์ง(ํ์ฌ๋ ์ ์ง๋ณด์๋ฅผ ์ํด์ ์ฃผ๋ก ์ฌ์ฉ) ์ค๋ ๊ธฐ๊ฐ๋์ ์ฌ์ฉ๋๊ณ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ์ ํฌ๋ ํ ๋ฒ ์ฌ์ฉํด๋ณด๊ธฐ๋ก ํ์ต๋๋ค.
๊ธฐ๋ณธ ์ธํ ๋ฐ ์ฌ์ฉ๋ฒ์ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด ์งํํ์ต๋๋ค.
| redux์ ๊ธฐ๋ณธ ๊ฐ๋
redux๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ํ๋์ JavaScript ๊ฐ์ฒด๋ก ๊ด๋ฆฌํ๊ณ , ์ก์ (Action)์ ํตํด ์ํ์ ๊ฐ์ ๋ณ๊ฒฝํฉ๋๋ค. store๋ ์ด ์ํ๋ฅผ ๋ณด์ ํ๋ฉฐ, ์ก์ ์ ๋์คํจ์น(dispatch)ํ์ฌ ์ํ๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
Redux Toolkit์์ createSlice ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ๋์(reducer)๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ configureStore ํจ์๋ฅผ ํตํด store๋ก ๊ฒฐํฉํ ์ ์์ต๋๋ค. store๋ ์ด๋ ๊ฒ ์์ฑ๋ ๋ฆฌ๋์์ ํจ๊ป ๋์ํ์ฌ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ํ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํฉ๋๋ค.
redux-toolkit์ ๋ํ ๋ ์์ธํ ์ค๋ช ๊ณผ ์ธํ ๊ณผ์ ์ redux-toolkit ๊ฒ์๊ธ์ ์ฐธ๊ณ ํด์ฃผ์ธ์.
sign-up
sign-in
create-post
read-post : pagination
update-post
delete-post
figma ๐ ๋ ์์ธํ ๋์์ธ์ ํผ๊ทธ๋ง ๋งํฌ์์ ํ์ธํ๊ธฐ
vercel ๐ ๊ตฌํ๋ ์ฌ์ดํธ์์ ํธํ๊ฒ ํ์ธํ๊ธฐ
github ๐ ์์ธํ ์ฝ๋ ํ์ธํ๊ธฐ
๐ชจ ๋ถ์๊ณผ ์ค๊ณ์ ๋ํด ๋ฌด์งํ๋...
API ๋ถ์๊ณผ ์ค๊ณ๋ฅผ ํ๊ธฐ ์ํด sequence diagram์ ๋ํด ์๊ฒ ๋์์ต๋๋ค.
์๊ธฐ๋ก๋ง ๋๋ด๋ ๋ถ๋ถ๋ค์ ํ๋ก ๊ทธ๋ ค๋ณด๋ฉด์ ์ด์ ๋ณด๋ค๋ api์ ํ๋ฆ์ ๋ํด์ ์ ํ์
์ ํ ์ ์์์ต๋๋ค. ๋ค๋ง ๊ฐ์ด ์งํํ๋ ํ๋ก์ ํธ์ด๋ค ๋ณด๋ ๊ฐ์ด ์๊ธฐ๋ฅผ ํ๋ฉด์ ๋ ๋ง์ ๊ฒ์ ๊ณต์ ํ์ด์ผ ํ๋๋ฐ ์ด ๋ถ๋ถ์ ๊ฐ๊ณผํ์ต๋๋ค.
์์ด์ดํ๋ ์์ ๋ณด๋ฉด์ "์ด ๊ธฐ๋ฅ์ด ๊ตฌํ ๊ฐ๋ฅํ ๊ฒ์ธ๊ฐ?"์ ๋ํด ์๊ธฐ๋ฅผ ๋๋ด์ด์ผ ํ๋ค๋ ํผ๋๋ฐฑ์ ๋ฐ๊ณ ์ด ๋ถ๋ถ์ ์ค์ ์ ์ผ๋ก ๋ํํ์ต๋๋ค.
๊ทธ ์ธ์๋ ์ด๋ค ๋ฐ์ดํฐ์ ๊ฐ๋ค์ด ์๋ต์ผ๋ก ์ฌ ๊ฒ ๊ฐ์์ง thunder client๋ก ์ฐ์ด๋ดค์ด์ผ ํ์ต๋๋ค. ๊ฒฐ๊ณผ์ ๋ง๊ฒ type์ ์ ์ํ๊ณ , ๋ชฉ๋ฐ์ดํฐ๋ ์ ๋ฌ๋๋ ๋ฐ์ดํฐ์ ํํ์ ๋์ผํ๊ฒ ๋ง๋ค์์ด์ผ api ์ฐ๊ฒฐ ์ ๊ธฐ๋ฅ ๊ตฌํํ ๋ถ๋ถ์ ๊ฑด๋๋ฆด ์ผ์ด ์๋๋ฐ...
response.data์ ํํ๋ฅผ ์์๋ก ์์ํด ๊ตฌํํ ๊ฒ์ด ํ๊ทผ์ด์์ต๋๋ค.
๐ฎ ๋ฏธํกํ ์ค๋น๋ก ์ธํ over engineering...
thunder client๋ก api ํธ์ถ ์ ์ด๋ค ํํ์ ๋ฐ์ดํฐ๊ฐ ์ค๋์ง ํ์ธ์ ํ์ง ์์ ์ํ๋ก ๋ชฉ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค์ด ๊ฐ๋ฐ ์ค ๋ก์ง์ ๋ค์ง์ด ์๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ์ต๋๋ค.
refactor๋ฅผ ์งํํ PR์ ์ดํด๋ณด๋ฉด ์ ๋ง ๋ง์ ํ์ผ๋ค์ ๋ณํ๊ฐ ์์์์ ํ์ธํ ์ ์์ต๋๋ค.
๋จ์ ๊ธฐ๋ฅ์ ์ธ ๋ถ๋ถ์ ํ๋ ์ฝ๋ฉ์ด ๋ฌธ์ ๊ฐ ์๋๋ผ API๋ฅผ ์ ๋ง ์ ๋ถ์ํ๋ค๋ฉด ์ด๋ ๊ฒ ๊ตฌํํ ์ ์์ ๋ฐฉ๋ฒ์ผ๋ก ์ด๋ ต๊ฒ(?) ๊ฐ๋ฐ์ ํ์ต๋๋ค.
ํนํ main-page์ post๋ค์ pagination์ผ๋ก ๋ณด์ฌ์ฃผ๋ ํ์ผ์์ ๊ณผ๋ํ ์์ง๋์ด๋ง์ด ๋ฐ์ํ์ต๋๋ค. BE์์ pagination๊ณผ ๊ด๋ จ๋ api๋ฅผ ๋ณด๋ด์ฃผ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ํ์ฉํ๋ค๋ฉด ์ข ๋ ๊ฐ๊ฒฐํ๊ณ ์ฝ๊ฒ ๋ก์ง์ ์งค ์ ์์์ต๋๋ค.
โ๏ธ over engineering์ ์ํ
{
id: "",
title : "",
content : "",
Post_img: ["", ""],
Comments: [{
id: "",
content: "",
User: {
id: "",
nickName: "",
profileImg: "",
},
createdAt: ""
}],
createdAt: ""
}
// method : GET (url : data/post)
{
data: {
title: "",
content: ""
},
id: "",
createdAt: "",
dataImage: [{"url": ""},{"url": ""}],
dataUser: {
data: { nickName: ""},
userId: "",
profile_url: ""
}
},
}
// before
export type Post = {
id: string
title: string
content: string
User: User
Post_img?: string[]
Comments?: Comment[]
createdAt: string
}
export type Comment = {
id: string
content: string
User: User
createdAt: string
}
//after
export type Post = {
id: string
data: { title: string; content: string }
dataUser: { data: { nickName: string }; profile_url: string; userId: string }
dataImage?: []
createdAt: string
}
export type Comment = {
id: string
data: { content: string }
dataUser: { data: { nickName: string }; profile_url: string; userId: string }
createdAt: string
}
export type Pagination = {
start: number
end: number
total: number
set: number
current: number
}
๋ฐ์ดํฐ์ ๊ตฌ์กฐ/ํํ์ type์ BE์์ ์ ๋ฌํด์ฃผ๋ ๊ฒ๊ณผ ๋๋ฌด ์ฐจ์ด๊ฐ ์๊ฒจ ์ฝ๋์ ๊ฑฐ์ ์ ๋ถ๋ฅผ ์์ ํด์ผ ๋์ต๋๋ค. ์ ๊ธฐ๋ณธ type์ผ๋ก ๋ณ๊ฒฝ ํ ์ฌ์ฉํ type์ ์๋์ ๊ฐ์ต๋๋ค.
export type TPostsResponse = {
data: Post[] | []
pageNation?: Pagination
}
export type TCommentsResponse = {
data: Comment[]
pageNation?: Pagination
}
๐ ๋ํ์ ์ธ ์ฌ๋ก๋ฅผ ๊ผฝ์๋ฉด...
| getPost
main์์ pagination์ผ๋ก ๊ฐ์ props๋ก ์ ๋ฌํด์ค ๋์ ๊ฐ์ ๋ณํ๊ฐ ์์ต๋๋ค.
MockData๋ก ์งํํ ๋์๋ ๊ฐ์ ํ๋ ํ๋ ์ ์ธํด ์ ๋ฌํด์ฃผ์๋ ๋ฐ๋ฉด API๋ก ์งํํ๊ฒ ๋๋ฉด์ ๋ณ๋๋ก ์ ์ธํ ๊ฐ๋ค ๋์ postList(data)์์ pagination ๊ฐ์์ ๋ฐ๋ก ์ ๋ฌ๋ง ํด์ค ์ ์์์ต๋๋ค.
// develop with MockData
<Pagination
listLength={listLength}
currentPage={currentPage}
perPage={perPage}
onPageChange={onPageChange}
/>
// develop with API
<Pagination
startPage={postList.pageNation?.start}
endPage={postList.pageNation?.end}
currentPage={postList.pageNation?.current}
totalPage={postList.pageNation?.total}
onPageChange={onPageChange}
/>
์๋๋ pagination.tsx
์์์ ๋ณํ์
๋๋ค.
// develop with MockData
const NumberButtons: number[] = Array.from({length: Math.min(pagesPerGroup, totalPage)}, (_, index) => startPage + index).filter(pageNumber => pageNumber <= totalPage);
// develop with API
const NumberButtons: number[] = Array.from({ length: endPage - startPage + 1 }, (_, index) => startPage + index)
pagination ๊ฐ์ BE์์ ๋ฐ๋ก ์ ๋ฌํด์ฃผ๊ณ ์์๊ธฐ ๋๋ฌธ์ ์ ๊ฐ ํ๋ํ๋ ์ ์ธํ ํ์๊ฐ ์๋ ๊ฐ๋ค์ด์์ต๋๋ค. ์ค๊ฐ์ refactor ๊ณผ์ ์์ type ๋ณ๊ฒฝ ํ pageination ๊ฐ์ ์ฌ์ฉ์ผ๋ก ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํ๊ณ ๊น๋ํด์ง ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
| postPost (createPost)
์๋ก์ด ๊ฒ์๊ธ์ POSTํ ๋ ์ด๋ฏธ์ง์ ์ ์ก ์์น ๋๋ฌธ์ ๊ฑฐ์ ํ๋ฃจ๋ฅผ ๊ณ ๊ตฐ๋ถํฌํ์ต๋๋ค.
formData์์ ์ด๋ฏธ์ง ๋ฐฐ์ด๋ ์๋ชป๋ ์์น๋ก ๋ณด๋ด๊ณ ์์ด post๋ ๊ฒ์๊ธ์ ์ด๋ฏธ์ง๊ฐ ํ๋ฉด์ render ๋์ง ์์์ต๋๋ค. mockData์ ์์น๋ก ๋ณด๋ด๋ ๊ฒฝ๋ก๋ฅผ ๋ฐ๊พธ์ง ์์ ์๋ชป๋ ๊ฒฝ๋ก๋ก ๋ณด๋ด๊ณ ์๋ค๋ 400๋ฒ๋ ์๋ฌ๋ฅผ ๋ง๋ฅ๋จ๋ ธ๋ ๊ฑฐ์์ต๋๋ค.
๊ฒฐ์ ์ ์ผ๋ก ๋ฌธ์ ๊ฐ ๋๋ ์ฝ๋๋ ์๋์์ต๋๋ค.
<PostDetailContent
postId={postDetail.data.id}
title={postDetail.data.data.title}
content={postDetail.data.data.content}
postImages={postDetail.data.dataImage} โ๏ธ
nickName={postDetail.data.dataUser.data.nickName}
profileImage={postDetail.data.dataUser.profile_url}
weekday={postDetail.data.createdAt}
isEditMode={isEditMode}
setIsEditMode={setIsEditMode}
/>
props๋ก postImages๋ฅผ ์ ๋ฌํด์ฃผ๋๋ฐ ์๋ชป๋ ์์น์ ์ ์ก์ด ๋๊ณ ์์๊ธฐ ๋๋ฌธ์ post๋ ๊ฒ์๊ธ์ ์ด๋ฏธ์ง๊ฐ get์ผ๋ก ๊ฐ์ ธ์์ ๋ ์ฝํ์ง ์๋ ์ด์๊ฐ ์์์ต๋๋ค.
๊ทธ๋๋ post๋ฅผ ํ๋ฉด์ formData์ ์ ์ฉํ ์ฌ์ฉ๋ฒ ํ๋๋ฅผ ์๋กญ๊ฒ ์๊ฒ๋๊ธฐ๋ ํ์ต๋๋ค.
// upload image : preview
const onUploadImage = (e: React.ChangeEvent<HTMLInputElement>) => {
const imageLists = e.target.files as FileList
let imageUrlLists: string[] = [...showImages]
for (let i = 0; i < imageLists.length; i++) {
const currentImageUrl = URL.createObjectURL(imageLists[i])
imageUrlLists.unshift(currentImageUrl)
}
if (imageUrlLists.length > 5) {
imageUrlLists = imageUrlLists.slice(0, 5)
alert("ํ ๋ฒ์ ์ด๋ฏธ์ง๋ฅผ 5๊ฐ ์ด์ ์ถ๊ฐํ์ค ์ ์์ต๋๋ค.")
}
setHasImage(true)
setShowImages([...imageUrlLists])
}
// send new post's data to BE
//before
const { handleCreatePost } = usePostActions()
const currentUser = useAppSelector(state => state.user[0])
const onSubmitCreatePost = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData()
formData.append("title", (e.currentTarget.elements.namedItem("title") as HTMLInputElement)?.value || "")
formData.append("content", (e.currentTarget.elements.namedItem("content") as HTMLInputElement)?.value || "")
for (let i = 0; i < showImages.length; i++) {
formData.append("images[]", showImages[i])
}
try {
await handleCreatePost(formData)
} catch (error) {
console.error("๊ฒ์๊ธ ๋ฑ๋ก ์ค ์๋ฌ ๋ฐ์:", error)
}
}
before
- ์ง์ ํผ ๋ฐ์ดํฐ์ ๊ฐ ํ๋๋ฅผ ์๋์ผ๋ก ์ถ๊ฐ
- ์ด๋ฏธ์ง๋ฅผ ๋ฐ๋ณต๋ฌธ์ ํตํด ์๋์ผ๋ก ํผ ๋ฐ์ดํฐ์ ์ถ๊ฐ
after
- FormData ์์ฑ์์ FormElement๋ฅผ ์ ๋ฌํ์ฌ ์๋์ผ๋ก ํผ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์ฑ
- ์ด๋ฏธ์ง๋ฅผ ์๋์ผ๋ก ํผ ๋ฐ์ดํฐ์ ์ถ๊ฐ
// after
const onSubmitCreatePost = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
try {
await handleCreatePost(formData)
} catch (error) {
alert("๊ฒ์๊ธ์ ๋ฑ๋กํ์ง ๋ชปํ์ต๋๋ค! ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.")
console.error("๊ฒ์๊ธ ๋ฑ๋ก ์ค ์๋ฌ ๋ฐ์:", error)
}
}
์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์ฒ์ ์๊ฒ ๋ ๋ถ๋ถ์ผ๋ก FormElement๋ฅผ ์ ๋ฌํ๋ฉด ํด๋น ํผ์ ๋ชจ๋ field๊ฐ FormData์ ์๋์ผ๋ก ์ถ๊ฐํ ์ ์์ต๋๋ค. form์ ์๋ ๋ชจ๋ ์
๋ ฅ field, file ๋ฑ์ FormData์ ํฌํจํ์ฌ ์ ์กํ๊ณ ์ ํ ๋ ์ ์ฉํฉ๋๋ค.
๊ฐ๋ณ์ ์ผ๋ก field๋ฅผ ์ถ๊ฐํ๊ณ ์ ํ ๋๋ append ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ์ฉํ๊ฒ ์ง๋ง, ๋ชจ๋ field๋ฅผ ํ๊บผ๋ฒ์ ์ถ๊ฐํ ๋์๋ ์์ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฒ์์ MMM ํ๋ก์ ํธ๋ฅผ ์งํํ ๋ ํ๋ ํ๋ append๋ก ์ ์กํ์๋๋ฐ... ๋์ค์ ๋ฆฌํฉํฐ๋งํ ๋ ์ด ๋ถ๋ถ์ ๊ผญ ํด์ฃผ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. JavaScript์์ ํ ๋ฒ์ ๋ณด๋ผ ๋์๋ type๋ง ์ ๊ฑฐํด์ฃผ๋ฉด ๋์ผํ๊ฒ ์ ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
const onSubmitCreatePost = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
try {
await handleCreatePost(formData);
} catch (error) {
alert("๊ฒ์๊ธ์ ๋ฑ๋กํ์ง ๋ชปํ์ต๋๋ค! ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.");
}
};
API๋ก๋ถํฐ ์ ๋ฌ๋ฐ๋ data์ ํํ๋ฅผ ์ ๋๋ก ํ์ธํ์ง ์์์ผ๋ก ๋ฐ์ํ๋ ๋ฌธ์ ๋ค์ ๋ผ์ ๋ฆฌ๊ฒ ๋๋ ํ๋ก์ ํธ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ฒ์์ mockData์ type๋ค์ BE๊ฐ ์ ๋ฌํด์ฃผ๋ data์ ํํ์ ๋ค๋ฅด๊ฒ ๊ตฌ์ฑ์ ํ๊ณ ์งํํ๋ค๋ณด๋ ํผ๋ธ๋ฆฌ์ฑ ๋จ๊ณ์์ ๋์์ธ๊ณผ ๋ก์ง์ ๊ฐ๋ฐํ๋๋ฐ ๊ฐ๋ฐ ๋จ๊ณ์์ ๋ค์ ๋ก์ง์ ์์ ํ๊ณ ์๋ ์ํฉ์ด ๋ฒ์ด์ก์ต๋๋ค.
API ๋ถ์๊ณผ ์ค๊ณ๋ฅผ ์ ๋๋ก ํ๋ค๋ฉด ๊ฐ๋ฐ ๋จ๊ณ์์๋ ๋ชฉ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ถ๋ง API๋ก ๋ณ๊ฒฝํด์ฃผ๋ ๋ฑ์ ์ผ๋ง ์งํํ๋ฉด ๋ผ์ ๊ธ๋ฐฉ ํ๋ก์ ํธ๋ฅผ ๋๋ผ ์ ์์์ํ ๋ฐ...
๊ฐ๋ฐ ์๊ฐ๋ง ๋ฐ์ ธ๋ณธ๋ค๋ฉด ์ ๋ง ์๊ฐ์ด ์๊น์ด ํ๋ก์ ํธ์์ง๋ง API ๋ถ์๊ณผ ์ค๊ณ์ ๋ํ ์ค์์ฑ์ ๊ทธ ์ด๋๋๋ณด๋ค ํ์คํ ์ฒด๊ฐํ ์ ์์๊ณ ์์ผ๋ก ์ด๋ป๊ฒ ์งํํด์ผ ํ ์ง ํ์คํ ์ ์ ์์๋ ๊ณ๊ธฐ๊ฐ ๋์์ต๋๋ค.
๐ over engineering์ผ๋ก ์ธํ ETA delay...
ETA๊ฐ ๊ณ์ ๋ค๋ก ๋ฐ๋ฆฐ ๋ฐ์๋ ํฌ๊ฒ 3๊ฐ์ง์ ์ด์ ๊ฐ ์์ต๋๋ค :
๏นข ์ค ์ฐํด๊ฐ ํฌํจ๋ ํ๋ก์ ํธ ๊ธฐ๊ฐ (3์ผ)
๋จผ์ API ์ค๊ณ์ ๋ถ์ ๊ทธ๋ฆฌ๊ณ auth ๋ก์ง์ ๋ํ ๊ณต๋ถ๊ฐ ๊ฐ๋ฐ ๊ธฐ๊ฐ ์์ ํฌํจ๋์ด ์์์ต๋๋ค.
์ด์ ํ๋ก์ ํธ์์ pain-point๋ฅผ ๋๊ผ๋ ๋ถ๋ถ๋ค์ ๋ํ ๊ณต๋ถ๋ฅผ ์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ณํํ์ต๋๋ค. sequence-diagram์ด๋ผ๋ ๊ฒ๋ ์๋กญ๊ฒ ์๊ฒ ๋์ด ๋ง๋ค์ด ๋ณด๊ณ auth ๋ก์ง์ ๋ํด ์์ธํ ์์๋ณด๋ ๊ธฐ๊ฐ์ด ํฌํจ๋์ด ์์ด ํ๋ก์ ํธ์ ๊ท๋ชจ์ ๋นํด ๊ธธ๊ฒ ETA๋ฅผ ์ค์ ํ์ต๋๋ค.
post-mobism์์ ์๋กญ๊ฒ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด 2๊ฐ ์์์ต๋๋ค.
Redux Toolkit์ผ๋ก ์ ์ญ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ๊ณผ ReactHookForm์ controller + Zod
Redux Toolkit์ ๋ํด ํ์์ ๋ค๋๋ฉด์ ๊ฐ๋จํ ๋ฐฐ์ด ์ ์ ์์ง๋ง ์ ๊ฐ ์ง์ ํ๋ก์ ํธ์ ์ฌ์ฉํด๋ณด๋ ๊ฒ์ ์ด๋ฒ์ด ์ฒ์์ด์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ TS์์ ํธํ์ฑ์ด ์ข์ง ์์ type๊ณผ ๊ด๋ จ๋ ์๋ฌ๋ ์ ๋ง ๋ง์ด ๋ฐ์ํ์ต๋๋ค.
API ๋ถ์ ๋ฏธํก์ผ๋ก ์ธํ over engineering์ผ๋ก ์ธํด ๊ฐ๋ฐ ๊ธฐ๊ฐ๋ ์์๋ณด๋ค 2๋ฐฐ ์ ๋ ๊ธธ์ด์ก๊ณ
์ค ์ฐํด๊ฐ ํฌํจ๋์ด ์๋ ๊ธฐ๊ฐ์๋ ํ์๋ณด๋ค ์ ์ ์๊ฐ์ ํฌ์ํด ์กฐ๊ธ ๊ธธ์ด์ง๊ธฐ๋ ํ์ต๋๋ค.
" post-mobism" ํ๋ก์ ํธ๋ API ๋ถ์๊ณผ ์ค๊ณ๊ฐ ์ผ๋ง๋ ์ค์ํ ๊ณผ์ ์ธ์ง, ์ด ๋ถ๋ถ์ด ์๋ชป ๋์์ ๋ ๊ฐ๋ฐ ๊ธฐ๊ฐ์ ์ด๋ ํ ์ํฅ์ ๋ฏธ์น๋์ง ๋ผ์ ๋ฆฌ๊ฒ ๋๊ผ์ต๋๋ค. ์๊ฐ์ ๊ณผํ๊ฒ ํฌ์ํ ๋งํผ sequence diagram๋ ๊ทธ๋ ค๋ณด๋ฉด์ ๋๋ฃ ๊ฐ๋ฐ์์ ์ด๋ค ์๊ธฐ๋ค์ ๋๋ ์ผ ํ๋์ง ์ ์ ์๋ ๊ณ๊ธฐ๊ฐ ๋์์ต๋๋ค. ๋ค์ ํ๋ก์ ํธ๋ถํฐ๋ ์ฒ์๋ถํฐ ๋์ฑ ํ์คํ response.data์ ํํ๋ฅผ ํ์ ํด ํผ๋ธ๋ฆฌ์ฑ์์ ๊ฐ๋ฐํ ์ฝ๋๋ฅผ ๊ฐ๋ฐ ๋จ๊ณ์์ ๊ณผ๋ํ๊ฒ ๋ณ๊ฒฝํ ์ผ์ด ์๊ฒ ํ๊ณ ์ถ์ต๋๋ค...!
์ด ์ธ์๋ ์ ๋ง ๋ง์ ์๊ฐ์ ๊ฐ์ ๋ฃ์ผ๋ฉด์ ๋ง์ ๊ธฐ๋ฅ๋ค์ ์ด์ ๋ณด๋ค ์ ์ด์ฉํ ์ ์๊ฒ ๋์์ต๋๋ค.
thunder client์ ๊ฐ๋ฐ์ ๋๊ตฌ์ network, application์ localStorage์ cookie ํญ์ ํตํด response๋ฅผ ํ์ธํ๋ ์ผ๋ค์ด ํนํ ๊ทธ๋ฐ ๊ฒ ๊ฐ์ต๋๋ค. ์ฒซ ํ๋ก์ ํธ์์๋ ๋๋ถ๋ถ console ์ฐฝ์ผ๋ก ํ์ธ์ ํ๊ฑฐ๋ ์...๐
์ ์ ๋๋์ง ํ์ธํ๊ธฐ ์ํด network, application์ ์์๋ก ๋ค์ด๊ฐ๋๋ ์ด์ ์ผ ๋น๋ก์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ์ต์ํด์ก์ต๋๋ค. ๐ฅฒ