๋ฌธ์
POST/PATCH Mutation ์์ฒญ์ ์บ์ฑ์ ๋ฌดํจํํ์ฌ ๋ฐ์ดํฐ์ ์ต์ ์ํ๋ฅผ ์ ์งํ๋๋ก ์ ์ฉํด๋์๋ค.
์ด๋ฏธ์ง ๋ฑ๋ก POST > ์ด๋ฏธ์ง ๋ชฉ๋ก GET > ์ธ๋ค์ผ ์ด๋ฏธ์ง PATCH > ์ ์ฒด ์ด๋ฏธ์ง๋ชฉ๋ก GET ์์๋ก ํธ์ถ๋์ด ๊ฐฑ์ ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋๋ก ์์ํ์ผ๋, PATCH Mutation ์์ฒญ ์ดํ์ GET ์ฟผ๋ฆฌ๊ฐ ์คํ๋์ง ์์์, ์ธ๋ค์ผ์ด๋ฏธ์ง๊ฐ ํ๋ฉด์ ๋ฐ๋ก ๋ฐ์๋์ง ์๋ ๋ฌธ์ ๊ฐ ์์๋ค.
Expected Behavior
POST > GET > PATCH > GET
Current Behavior
- POST > GET > PATCH (๋ธ๋ผ์ฐ์ ๋คํธ์ํฌ)
- POST > PATCH > GET (์ฝ์)
์ปดํฌ๋ํธ ์ฝ๋
//ImageContainer.tsx
์๋ฒ์ ์์ฒญ ๋ณด๋ด๋ ์ฝ๋ ์กด์ฌ
์ธ๋ค์ผ ์ค์ ํ๋ ํจ์
const [updateSiteImgThumbnail] = useUpdateSiteImgThumbnailMutation();
const handleUpdateThumbnailImg = (photoId: string) => {
updateSiteImgThumbnail({ id: siteId, photoId })
.unwrap()
.catch(() => toast('๋ํ์ด๋ฏธ์ง ์ค์ ์๋ฌ'));
};
์ ๋ก๋ํ ์ด๋ฏธ์ง๋ฅผ ์์ฑ ์์ฒญ ๋ณด๋ด๋ ํจ์
์ธ๋ค์ผ ์ด๋ฏธ์ง์ด๋ฉด ์ธ๋ค์ผ ์ค์ ํ๋ ์์ฒญ์ ๋ณด๋
const [postSiteImgWithFile, { isLoading: isLoadingPostSiteImgWithFile }] =
usePostSiteImgWithFileMutation(); //rtk query mutation hook
const handleCreatePhoto = async (
reqbody: FormData,
isThumbnailImg: boolean,
) => {
try {
const res = await postSiteImgWithFile({
id: siteId,
fileData: reqbody,
}).unwrap();
if (isThumbnailImg) {
const photoId = res.id;
handleUpdateThumbnailImg(photoId);
}
} catch {
toast.error('์ด๋ฏธ์ง ๋ฑ๋ก ์๋ฌ. ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์');
}
};
//ImageModal.tsx
์ด๋ฏธ์ง ๋ฑ๋ก์ ์คํ๋๋ ํจ์
const handleConfirmPhotoUpload = () => {
const newFormData = new FormData();
newFormData.append('name', photoForm.description);
if (photoForm.imgFile) {
newFormData.append('file', photoForm.imgFile);
}
if (type === 'create') {
void props.onPostPhoto(newFormData, isThumbnailImg).then(() => {
resetModalState();
onChangeOpenPhotoModal();
});
}
};
์์์ `handleCreatePhoto` ํจ์๋ฅผ ImageModal ์ปดํฌ๋ํธ์์ `onPostPhoto` props ๋ค์ด๋ฐ์ผ๋ก ๋ฐ์์ ์ฒ๋ฆฌํ๋ค.
Rtk Query API ์ฐ๋ ์ฝ๋
//์ฌ์ง ๋ฑ๋ก
postSiteImgWithFile: builder.mutation<
SitePhotoResponse,
{ id: string; fileData: FormData }
>({
query: ({ id, fileData }) => ({
url: `/sites/${id}/photos`,
method: 'POST',
body: fileData,
}),
invalidatesTags: ['Photo'],
}),
//๋ํ์ฌ์ง ์์
updateSiteImgThumbnail: builder.mutation({
query: ({ id, photoId }: { id: string; photoId: string }) => ({
url: `/sites/${id}/photos?thumbnailPhotoId=${photoId}`,
method: 'PATCH',
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
}),
invalidatesTags: ['Photo'],
}),
invalidateTags์ ๋ช ์ํด๋๊ฒ ์ฒ๋ผ ์ด๋ฏธ์ง๋ฅผ ๋ฑ๋กํ๊ฑฐ๋ ์ธ๋ค์ผ ์์ ์์ฒญ์ ๋ณด๋ผ๋๋ ์บ์ ๋ฌดํจํ๋ฅผ ์ ์ฉํด์ฃผ์ด mutation ์์ฒญ์๋ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ๋๊ณ ๋ค์ get ์์ฒญ์ ๋ฐ์์ฌ ์ ์๋๋ก ์งฐ๋ค.
msw handler ์ฝ๋
//์ด๋ฏธ์ง ๋ฑ๋ก
rest.post('/sites/:id/photos', async (req, res, ctx) => {
//ํ๋ฐํธ์์ ๋ณด๋ด๋ ์์ฒญ ๋ฉํฐํํธ ํญ๋ชฉ: file, name
const newPhoto = addSitePhoto(req.body as SitePhotoRequest);
return res(ctx.status(201), ctx.json(newPhoto));
}),
//์ด๋ฏธ์ง ์ฌ๋ค์ผ ์ค์
rest.patch('/sites/:id/photos', async (req, res, ctx) => {
const thumbnailPhotoId = req.url.searchParams.get('thumbnailPhotoId');
if (thumbnailPhotoId) {
updateThumbnail(thumbnailPhotoId);
}
return res(ctx.status(204));
}),
handler ํ์ผ ๋ด๋ถ์์ mockPhotoList๋ฅผ ๋ณ์๋ก ๊ด๋ฆฌํ์ฌ addSitePhoto ํจ์๋ updateThumbnail ํจ์๋ฅผ ํตํด ๊ด๋ฆฌํ๊ณ ์๋ mock data ๋ชฉ๋ก์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๊ฒ ๋ง๋ ๋ค. addํจ์๋ ๋ชฉ๋ก์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ๊ณ , ์ธ๋ค์ผ update ํจ์๋ ๋ชฉ๋ก์์ ์ธ๋ค์ผ ์ค์ ์ ์ ๋ฐ์ดํธํ๊ณ ์ํ๋ item์ ์ฐพ์์ thumbnail ์์ฑ๊ฐ์ true๋ก ๋ฐ๊ฟ์ค๋ค.
๋๋ฒ๊น ์ผ๋ก ์์ธ ์ฐพ๊ธฐ
๋๋ฒ๊น
Redux ๋๋ฒ๊น ํด๋ก ์ดํด๋ณด๋, POST > PATCH > GET ์์ผ๋ก ์คํ๋๋ค๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
1. post ์์ฒญ
2. patch ์ ๋ฐ์ดํธ ์์ฒญ
๊ทธ๋ฐ๋ฐ ์ด๋ patch ์์ฒญ์ ์๋ต์ธ isSuccess flag๋ฅผ ํ์ธํด๋ณด๋ false ์์ ํ์ธํ ์ ์์๋ค.
3. get ์์ฒญ
๋ฐ๋ผ์ get์์ฒญ์ ์๋ต๊ฐ์ patch ์ ๋ฐ์ดํธ ์์ฒญ์ด ๋ฐ์๋์ง ์์ post์์ฒญ ์ดํ์ ์๋ต๊ฐ์์ ํ์ธํ ์ ์์๋ค.
์ PATCH ์์ฒญ์ ์๋ต์ํ๊ฐ false๋ก ๋จ๋ ๊ฒ์ผ๊น? PATCH ์์ฒญ์ ์๋ต์ํ flag๊ฐ success ๋ผ๋ฉด, RTK Query๊ฐ PATCH ์์ฒญ์ ๋ํ ์๋ต์ ์ ๋๋ก ๋ฐ์๋ค๊ณ ์ธ์ํ ๊ฒ์ด๋ฏ๋ก ์ฟผ๋ฆฌ๊ฐ ์์ํ๋๋๋ก PATCH ์ดํ์๋ ์ ๋๋ก ์บ์ฑ์ฒ๋ฆฌ๋ฅผ ํด์ ์๋ก ์ ๋ฐ์ดํธ๋ GET ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ผํ ๊ฒ์ผ๋ก ์์๋๋ค.
์์ธ
์ด๋ฏธ์ง ์ฌ๋ค์ผ ์์ฒญํ๋ api์ ์๋ต๊ฐ์ 204์ด๋ค. 204 ("No Content")๋ ์๋ต๋ฐ๋์ ์๋์ ์ผ๋ก ์๋ฌด๊ฒ๋ ํฌํจํ์ง ์์๋ ์ฌ์ฉํ๋ค๋ ๊ท์น์ด์๋ค.
REST API ์๋ต ์ํ์ฝ๋ 204 ๊ท์น
204 ์ํ ์ฝ๋๋ ๋ณดํต PUT, POST, DELETE ์์ฒญ์ ๋ํ ์๋ต์ผ๋ก ์ฌ์ฉ๋๋๋ฐ, REST API๊ฐ ์๋ต ๋ฉ์์ง์ ๋ฐ๋์ ์ด๋ ํ ๋ฉ์์ง๋ ํํ์ ํฌํจํด์ ๋ณด๋ด์ง ์์ ๋ ์ฌ์ฉํ๋ค.
RTK Query๋ ์๋ฒ ์๋ต์ ์ํ์ฝ๋์ ๋ฐ๋ผ `isSuccess` ์ํ ํ๋๊ทธ๋ฅผ ์ค์ ํ๋ค. ํ์ง๋ง 204 ์ํ์ฝ๋์ ๊ฒฝ์ฐ๋ Content๊ฐ ์๋ ๋น ์๋ต์ ์๋ฒ์์ ์ฃผ๋ฏ๋ก RTK๊ฐ ํด๋น ์๋ต์ ๋ํด ์ฑ๊ณต ์ํ๋ก ์ธ์ํ์ง ๋ชปํ๋ ๋ฌธ์ ๊ฐ ์๊ธด ๊ฒ ๊ฐ๋ค.
์ค์ ๋ก handler ์ฝ๋ ์๋ต ์ํ๊ฐ์ 200์ ์์์ ์๋ต๊ฐ์ ๋ฆฌํด์์ผ์ฃผ๋ ์ ์ ์๋ํ๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค. ๊ทธ์น๋ง ์ค์ ์๋ฒ REST API์ ์คํ๋๋ก 204 ์๋ต๊ณผ ๋ง์ถฐ๋๊ธฐ์ํด handler๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ํ์ํ๋ฅผ ์ ์งํ๋ ๋ฐฉ๋ฒ์ผ๋ก ํด์ผํด์ ๋ค๋ฅธ ํด๊ฒฐ์ฑ ์ ์ฐพ์์ผํ๋ค!
๋ธ๋ผ์ฐ์ ๋คํธ์ํฌ ํญ๊ณผ ์ฝ์ ์์ฒญ ์์์ ์ฐจ์ด
์ถ๊ฐ๋ก ์ฝ์์ด๋ ๋๋ฒ๊น ํด๋ก ๋๋ฒ๊น ํ์๋ API์ ์คํ์์๊ฐ POST > GET > PATCH๊ฐ ์๋ POST > PATCH > GET ์์๋ก ๋ฐ์ํ๋ ๋ฌธ์ ๋ ์์ผ๊น.. ?
๋ธ๋ผ์ฐ์ ๋คํธ์ํฌ ํญ์์๋ ์์๋๋๋๋ก POST > GET > PATCH ์์๋ก ๋ฐ์ํ์์ง๋ง, ์ฝ์๊ณผ ๋๋ฒ๊น ํด์ ๋ชจ๋ POST > PATCH > GET ์์๋ก ์ฐํ๋ค.
์ฐพ์๋ณด๋, ๋คํธ์ํฌ ํญ์์๋ API์ ์์ฒญ์ ์๋ต์ด ์ค๋ ์์๋๋ก ์์ฒญ์ด ํ์๋๋ค.
์๋ต์๊ฐ์ด ์งง์ ์์ฒญ์ด ์๋ฃ๋๋ฉด ๋จผ์ ๋ํ๋๋๊ฒ์ด๊ณ , ์๋ต์ด ๊ธธ๋ฉด ์๋ฃ๋ ์์ ์ ๊ธฐ์ค์ผ๋ก ํ์๋๋ค.
๋ฐ๋ฉด, ์ฝ์์์๋ ์ฝ๋๊ฐ ์คํ๋๋ ์์๋๋ก ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋๋ค. ์์ฒญ ์๋ต์๊ฐ๊ณผ๋ ๋ฌด๊ดํ๊ฒ ์์ฒญ์ ๋ฐ์์ํค๋ ์์ ์ ๋ก๊ทธ๊ฐ ์ฐํ๋ฏ๋ก, ์์ฒญ์ ์๋ฃ ์์์๋ ๋ฌด๊ดํด๋ณด์ผ ์ ์๋ค. ์ปดํฌ๋ํธ ์ฝ๋์์ ์ฒ๋ผ ๋จผ์ POST์์ฒญ์ด ๊ฐ๊ณ ๊ฑฐ์ ๋์์ PATCH ์์ฒญ์ ์คํํ๊ธฐ์ POST ์ดํ์ PATCH ๊ฐ ๋ฐ๋ก ์ฐํ๊ณ , ์ดํ์ POST ์์ฒญ์ผ๋ก ์ ๋ฐ์ดํธ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ GET์ด ํ์๋๋ค.
TOBE
์ฌ์ค์ POST์ดํ์ GET์ ๋ฐ๋ก ํด์ฌํ์๊ฐ์๊ณ , POST > PATCH ์์ฒญ ์ดํ์ GET์ ํ๋ฒ ํด์ค๋ ๊ฒ ๋ฆฌ์์ค๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๋ฐฉ๋ฒ์ด์ด๋ค. ์ ์ด์ ์์ํ๋๋๋ก ์คํ์์๋ฅผ POST > GET > PATCH > GET ๊ฐ ์๋, POST, PATCH ์ดํ ์ ๋ฐ์ดํธ๊ฐ ๋ฐ์๋ GET์ ๋ฐ์์ค๋๋ก ์ฟผ๋ฆฌ์ ์บ์ฑ ์ ๋ต์ ๋ฐ๊ฟ ํ์๊ฐ ์์๋ค.
๋ฐ๋ผ์, ์ฌ์ง๋ฑ๋ก POST, ์ธ๋ค์ผ ์์ PATCH ์์ฒญ์ ์ฟผ๋ฆฌ ์คํ ์์๋ฅผ ์๋์ผ๋ก ์ ์ดํ๊ธฐ์ํด ๊ธฐ์กด์ invalidateTags์ธ ์บ์๋ฌดํจํ ํ๊ทธ๋ฅผ ์ญ์ ํ๊ณ , ์ฟผ๋ฆฌ์ ์คํ์์๋ฅผ hook์์ returnํด์ฃผ๋ refetch๋ผ๋ ํจ์๋ฅผ ํตํด ์๋์ผ๋ก ์ ์ดํด๋ ๊ฒ์ผ๋ก ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๋ค. ์ด์ ์ด๋ฏธ์ง ๋ฑ๋ก POST > (์ธ๋ค์ผ ์ด๋ฏธ์ง PATCH) > ์ ์ฒด ์ด๋ฏธ์ง๋ชฉ๋ก GET์ผ๋ก ์บ์ฑ์ด ์ ๋ฐ์ ๋๋ค.
https://redux-toolkit.js.org/rtk-query/usage/cache-behavior#default-cache-behavior