
Supabase์ Storage๋ฅผ ์ด์ฉํด ํ๋กํ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๊ณ ๋ถ๋ฌ์ค๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ฉด์,
React Query์ Zustand Store, API ์๋น์ค ๋ ์ด์ด๋ฅผ ์ญํ ๋ณ๋ก ๋ช
ํํ๊ฒ ๋ถ๋ฆฌํ ๊ตฌ์กฐ๋ฅผ ์๊ฐํฉ๋๋ค.
์ฒ์์๋ ์ปดํฌ๋ํธ ๋ด๋ถ์์ Supabase API๋ฅผ ์ง์ ํธ์ถํ๊ณ ,
์
๋ก๋๋ ์ด๋ฏธ์ง ์กฐํ ์ํ๋ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๊ณ ์์์ต๋๋ค.
ํ์ง๋ง ๋ค์๊ณผ ๊ฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฃ :
์ด๋ฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์ญํ ์ ๋ถ๋ฆฌํ์ต๋๋ค.
| ์ฑ ์ | ์์น | ์ค๋ช |
|---|---|---|
| API ํธ์ถ | profileService | Supabase ํธ์ถ๋ง ๋ด๋น (์์ ํจ์) |
| React Query | useProfileImageSuspenseQuery, useUploadProfileImageMutation | ์ํยท์๋ฌ ์ฒ๋ฆฌยท์บ์ ๋ฌดํจํ ํฌํจ |
| UI ์ํ | ์ปดํฌ๋ํธ ๋ด๋ถ | ํ์ผ ์ ํ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ฑ UI ์ ์ฉ ์ํ |
| ์ฌ์ฉ์ ์ ๋ณด | useUserStore | ์ํ ์ ์ญ ์ ์ฅ์์์ ๊ด๋ฆฌ |
profileService.ts)export const profileService = {
async uploadProfileImage(file: File, userId: string) {
const { data, error } = await supabase.storage
.from('images')
.upload(`profile/${userId}`, file, { upsert: true })
if (error) throw error
return data
},
async getProfileImageUrl(userId: string): Promise<string> {
const { data } = supabase.storage.from('images').getPublicUrl(`profile/${userId}`)
if (!data?.publicUrl) throw new Error('์ด๋ฏธ์ง URL์ด ์์ต๋๋ค.')
return data.publicUrl
},
}
useProfileImageSuspenseQuery.ts, useUploadProfileImageMutation.ts)export const useProfileImageSuspenseQuery = (profileId: string) =>
useSuspenseQuery({
queryKey: profileKeys.image(profileId),
queryFn: () => profileService.getProfileImageUrl(profileId),
})
export const useUploadProfileImageMutation = (profileId: string) => {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ file }) => profileService.uploadProfileImage(file, profileId),
meta: {
toastError: true,
toastSuccess: true,
toastSuccessMessage: 'ํ๋กํ ์ด๋ฏธ์ง๊ฐ ์ ์์ ์ผ๋ก ์
๋ก๋๋์์ต๋๋ค',
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: profileKeys.detail(profileId) })
queryClient.invalidateQueries({ queryKey: profileKeys.image(profileId) })
},
retry: 0,
})
}
const profileId = useUserStore((s) => s.profileId)
const { data: uploadedUrl } = useProfileImageSuspenseQuery(profileId)
const uploadMutation = useUploadProfileImageMutation(profileId)
uploadedUrl๋ง ์์ผ๋ฉด ์ด๋ฏธ์ง๋ฅผ ๊ทธ๋ฆด ์ ์๊ณ uploadMutation.mutate({ file })๋ง ํธ์ถํ๋ฉด ๋| ํญ๋ชฉ | ๋ฆฌํฉํฐ๋ง ์ | ๋ฆฌํฉํฐ๋ง ํ |
|---|---|---|
| API ํธ์ถ ์์น | ์ปดํฌ๋ํธ ๋ด๋ถ | profileService |
| ์ํ ๊ด๋ฆฌ | ์ปดํฌ๋ํธ์์ ์ง์ | React Query๊ฐ ์บ์ ๊ด๋ฆฌ |
| ๊ด์ฌ์ฌ ๋ถ๋ฆฌ | ํผ์ฌ๋์ด ์์ | ์๋น์ค / ์ฟผ๋ฆฌ / UI ๋ช ํ ๋ถ๋ฆฌ |
| ์ฌ์ฌ์ฉ์ฑ | ๋ฎ์ | ๋์ (์ด๋์๋ ํ ๋ง ํธ์ถํ๋ฉด ๋จ) |
| ์ ์ง๋ณด์ | ์ค๋ณต ์ฝ๋ ๋ง์ | ๋ก์ง ํ ๊ณณ์์ ํตํฉ ๊ด๋ฆฌ |
์ด๋ฒ ๊ตฌ์กฐ ๋ฆฌํฉํฐ๋ง์ ํตํด ์ปดํฌ๋ํธ๋ ์์ UI๋ง ๋ด๋นํ๋๋ก ์ ๋ฆฌํ๊ณ ,
๋น์ฆ๋์ค ๋ก์ง๊ณผ API ํธ์ถ์ ํจ์ดํ ์๋น์ค ํจ์์ React Query ํ
์ผ๋ก ๋ถ๋ฆฌํ์ต๋๋ค.
๐ ํ์ํ ๋ฐ์ดํฐ๋ง ๋๊ธฐ๋ฉด ๋๋๊นโฆ
const uploadMutation = useUploadProfileImageMutation(profileId) uploadMutation.mutate({ file })
์๋น์ค์ ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ์ด๋ฐ ๊ตฌ์กฐ๊ฐ ํ์
๊ณผ ์ ์ง๋ณด์์์ ์ง๊ฐ๋ฅผ ๋ฐํํฉ๋๋ค.
๋น์ ์ ํ๋ก์ ํธ์๋ ์ด ๊ตฌ์กฐ๋ฅผ ํ๋ฒ ์ ์ฉํด๋ณด์ธ์! ๐