-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
π λ©ν λ 리뷰 #83
base: review-branch
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π μλ μ
- κ·λͺ¨ λ§μΆ°μ μ μ ν λλ ν΄λ ꡬ쑰
- axios μΈν°μ ν°λ₯Ό ν΅ν λͺ¨λν
- κ³΅ν΅ μ»΄ν¬λνΈ λΆλ¦¬, μ νΈ ν¨μ μ¬μ¬μ©
π οΈ κ°μ ν μ
- μλ¬μ²λ¦¬ κ°ν, κ³΅ν΅ μλ¬μ κ°λ³ μλ¬λ₯Ό μ¬μ©ν μ μλ κ΅¬μ‘°λ‘ λ³κ²½(μ½λ μ½λ©νΈ 1-n μ°Έκ³ )
- λλΆλΆμ μλ¬λ κ³΅ν΅ μ²λ¦¬λ‘ ν΄κ²°
- νΉλ³ν μ²λ¦¬κ° νμν κ²½μ°λ§ κ°λ³ APIμμ μ²λ¦¬
- μ½λ μ€λ³΅μ μ΅μννλ©΄μλ μ μ°ν μλ¬ μ²λ¦¬ κ°λ₯
- 컀μ€ν ν μ ν΅ν λ°μ΄ν° μ²λ¦¬ λ‘μ§ λΆλ¦¬(μ½λ μ½λ©νΈ 2-n μ°Έκ³ )
λ°μ΄ν° νλ¦μ΄ λͺ νν΄μ§
- API β 컀μ€ν ν β μ»΄ν¬λνΈ
- undefined μ²λ¦¬κ° 컀μ€ν ν μμ μ΄λ£¨μ΄μ§
μ»΄ν¬λνΈ μ± μμ΄ λΆλ¦¬λ¨
- λ°μ΄ν° fetchingμ 컀μ€ν ν μμ
- UI λ λλ§μ μ»΄ν¬λνΈμμ
μλ¬ μ²λ¦¬κ° 체κ³μ
- λ‘λ©/μλ¬/λΉ μν μ²λ¦¬κ° λͺ νν¨
- μ¬μ©μμκ² μ μ ν νΌλλ°± μ 곡
νμ μμ μ± ν보
- μ»΄ν¬λνΈμμλ μ΄λ―Έ νμ μ΄ λ³΄μ₯λ λ°μ΄ν° μ¬μ©
- undefined μ²΄ν¬ λΆνμ
μ΄λ° λ°©μμΌλ‘ ꡬννλ©΄ μ½λκ° λ μμ μ μ΄κ³ μ μ§λ³΄μνκΈ° μ¬μμ§ κ² κ°μ΅λλ€.
μΆκ°μ μΈ μΆμ²
-
.prettierrc μ€μ νμΌκ³Ό μλν° autosave λ₯Ό ν΅ν μ½λ 컨벀μ μλ μ μ©
μ°Έκ³
https://velog.io/@kcs0702/VSC-%EC%9E%90%EB%8F%99%EC%A0%95%EB%A0%AC-auto-save-prettier-%EC%84%A4%EC%A0%95%EB%B2%95 -
API νμ μΆκ°
export interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
export interface ApiError {
code: string;
message: string;
status: number;
}
// API μν μ½λ μμν
export const API_STATUS = {
SUCCESS: 200,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
SERVER_ERROR: 500,
} as const;
// μλ¬μ²λ¦¬ μμν€μλ μ΄μ©
switch (status) {
case API_STATUS.UNAUTHORIZED:
return 'λ‘κ·ΈμΈμ΄ νμν©λλ€.';
case API_STATUS.FORBIDDEN:
return 'μ κ·Ό κΆνμ΄ μμ΅λλ€.';
case API_STATUS.NOT_FOUND:
return 'μμ²νμ μ 보λ₯Ό μ°Ύμ μ μμ΅λλ€.';
case API_STATUS.SERVER_ERROR:
return 'μλ² μ€λ₯κ° λ°μνμ΅λλ€.';
default:
return 'μ μ μλ μ€λ₯κ° λ°μνμ΅λλ€.';
ν΄λꡬ쑰 μ°Έκ³ μλ£
- FSD λΌλ κ΅¬μ‘°κ° μλ€λ κ²λ μ°Έκ³ , νμ¬ μ λͺ
ν λλ ν 리 λΆλ₯ κΈ°μ€μ΄λΌ μ°Έκ³ μ λ ν΄λ³΄λ©΄ μ’μ. μμμλ λμ€μ§λ§ λλ ν 리 ꡬ쑰 λΆλ₯μ μ λ΅μ μμ. λΆνΈν λ κ°μ μ μν΄ λλλκ² μ λ΅.
https://www.youtube.com/watch?v=64Fx5Y1gEOA
return response.data; | ||
}; | ||
|
||
// 리뷰 μμ± |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 1-2 κ°λ³ μλ¬ μ²λ¦¬μ κ²½μ° μλμ κ°μ΄ μ²λ¦¬νλ©΄ λ©λλ€.
export const postReview = async (data: ReviewData): Promise<Review> => {
try {
const response = await authInstance.post('/api/v1/review', data);
return response.data;
} catch (error) {
// 리뷰 μμ± μ νΉλ³ν μ²λ¦¬ν΄μΌ νλ μλ¬λ§ μ¬κΈ°μ μ²λ¦¬
if (error.response?.status === 400) {
throw new Error('리뷰 λ΄μ©μ νμΈν΄μ£ΌμΈμ.');
}
// λλ¨Έμ§λ κ³΅ν΅ μλ¬ μ²λ¦¬λ‘ μ ν
throw error;
}
};
createdAt: '2024-11-12T14:00:00', | ||
}, | ||
]; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- data undefined μλ¬ μ²λ¦¬
2-1 컀μ€ν hooks λμ
- src/hooks/useWorkplaceReview.ts μμ±
import { useQuery } from '@tanstack/react-query';
import { ReviewData } from '../types';
export const useWorkplaceReview = (workplaceId: string) => {
const { data, isLoading, error } = useQuery({
queryKey: ['workplaceReview', workplaceId],
queryFn: () => getWorkplaceReview(workplaceId),
retry: 1,
});
return {
reviews: data ?? [], // undefinedμΌ κ²½μ° λΉ λ°°μ΄ λ°ν
isLoading,
error: error as Error | null,
};
};
- 2-2 hooks λμ ν WorkPlaceReview μμ
import { useWorkplaceReview } from '@/hooks/useWorkplaceReview';
const WorkPlaceReview = ({ workplaceId }: { workplaceId: string }) => {
const { reviews, isLoading, error } = useWorkplaceReview(workplaceId);
if (isLoading) {
return (
<div className='w-custom flex justify-center'>
<p>리뷰λ₯Ό λΆλ¬μ€λ μ€...</p>
</div>
);
}
if (error) {
return (
<div className='w-custom flex justify-center'>
<p className='text-subfont'>리뷰λ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.</p>
</div>
);
}
if (reviews.length === 0) {
return (
<div className='w-custom flex justify-center'>
<p className='text-subfont'>μμ±λ λ¦¬λ·°κ° μμ΅λλ€.</p>
</div>
);
}
return (
<div className='w-custom'>
{reviews.map((item) => (
<div
key={item.reviewId}
className='border-b border-subfont py-[16px] last:border-none'
>
<ReviewComponent review={item} />
</div>
))}
</div>
);
};
createdAt: string; | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 2-3 ReviewComponentμμμ λ°μ΄ν° μ²λ¦¬
interface ReviewComponentProps {
review: ReviewData;
}
const ReviewComponent = ({ review }: ReviewComponentProps) => {
// reviewλ μ΄λ―Έ νμ
μ΄ λ³΄μ₯λ μν
const { userName, reviewRating, content, createdAt } = review;
return (
<div>
<h3>{userName}</h3>
<div>{reviewRating}</div>
<p>{content}</p>
<span>{createdAt}</span>
</div>
);
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tanstack-query μ λν΄μ μΆκ°μ μΌλ‘ 리뷰 λ¬μμ΅λλ€.
import { getPossibleTime } from '@apis/workplace'; | ||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||
import { PossibleTime } from '@typings/types'; | ||
|
||
type ModifyDateType = { studyRoomId: number; checkDate: Date }; | ||
|
||
export const useGetPossibleTime = (studyRoomId: number, checkDate: Date) => { | ||
const { data, isLoading, isError } = useQuery<PossibleTime>({ | ||
queryKey: ['studyroomDetail', studyRoomId, checkDate], | ||
queryFn: () => getPossibleTime(studyRoomId, checkDate), | ||
}); | ||
|
||
return { data: (data ?? {}) as PossibleTime, isLoading, isError }; | ||
}; | ||
|
||
export const usePossibleTimeMutation = () => { | ||
const queryClient = useQueryClient(); | ||
|
||
return useMutation<PossibleTime, Error, ModifyDateType>({ | ||
mutationFn: ({ studyRoomId, checkDate }) => | ||
getPossibleTime(studyRoomId, checkDate), | ||
onSuccess: (_, variables) => { | ||
const { studyRoomId, checkDate } = variables; | ||
queryClient.invalidateQueries({ | ||
queryKey: ['studyroomDetail', studyRoomId, checkDate], | ||
}); | ||
}, | ||
onError: (error) => { | ||
console.log(error); | ||
}, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useGetPossibleTimeμ κ²½μ° λ°μ΄ν° μ‘°νκ° λͺ©μ μΌλ‘ 보μ. λͺ©μ μ λ§λ λ©μλ μ¬μ©μΌλ‘ μ½λ κ°μν
- νμ¬ useMutationμμ GET μμ²μ μ¬μ©νκ³ μμ΄ μν μ΄ λ§μ§ μμ΅λλ€.
- useMutationμ μ£Όλ‘ λ°μ΄ν° μμ±(POST), μμ (PUT/PATCH), μμ (DELETE)μ κ°μ μλ²μ μνλ₯Ό λ³κ²½νλ μμ μ μ¬μ©λ©λλ€.
- νν°λ§ μ‘°κ±΄μ΄ λ³κ²½λ λλ useQueryμ queryKeyλ₯Ό λ³κ²½νμ¬ μλμΌλ‘ λ°μ΄ν°λ₯Ό μ‘°ννκ² νλ κ²μ΄ λ ν¨μ¨μ μ λλ€. κ·Έλ κ² λλ©΄ invalidateQueries μμ μ¬μ©ν νμκ° μμ΅λλ€.
import { getPossibleTime } from '@apis/workplace';
import { useQuery } from '@tanstack/react-query';
import { PossibleTime } from '@typings/types';
export const useGetPossibleTime = (studyRoomId: number, checkDate: Date) => {
const { data, isLoading, isError } = useQuery<PossibleTime>({
queryKey: ['studyroomDetail', studyRoomId, checkDate],
queryFn: () => getPossibleTime(studyRoomId, checkDate),
enabled: !!studyRoomId && !!checkDate,
});
return {
data: (data ?? {}) as PossibleTime,
isLoading,
isError
};
};
λ³κ²½ μΆμ²λ‘μ§ μ€λͺ
- useMutation, λΆνμν invalidateQueries μ κ±°
- queryKeyμ studyRoomIdμ checkDateλ₯Ό ν¬ν¨μμΌμ μ΄ κ°λ€μ΄ λ³κ²½λ λλ§λ€ μλμΌλ‘ μλ‘μ΄ λ°μ΄ν°λ₯Ό μ‘°νν©λλ€.
useQuery, useMutation μ¬μ© μ 리
useQuery μ¬μ© μμ
- λ°μ΄ν° μ‘°ν (GET)
- νν°λ§λ λ°μ΄ν° μ‘°ν
- νμ΄μ§λ€μ΄μ
- μ€μκ° λ°μ΄ν° ν΄λ§
useMutation μ¬μ© μμ
- λ°μ΄ν° μμ± (POST)
- λ°μ΄ν° μμ (PUT/PATCH)
- λ°μ΄ν° μμ (DELETE)
- μλ²μ μνλ₯Ό λ³κ²½νλ μμ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
κΆκΈμ¦μ΄ λͺ ννκ² ν΄κ²°λμ΅λλ€, κ°μ¬ν©λλ€!! 리ν©ν λ§μ λ°μν΄λ³΄κ² μ΅λλ€ :)
β¨ Feat : μμ νμ΄μ§ κΈ°λ₯ ꡬν 1μ°¨
β¨ Feat, β»οΈ Refactor : μ§λ κ°μ λ° μ½λ 리뷰 리ν©ν λ§ μ μ©
β»οΈ Refactor : μ§λ λ° κ²μ, μμΈνμ΄μ§ κ°μ
β»οΈ Refactor : S3 μ΄λ―Έμ§, ν ν°, μ±ν κ°μ
β»οΈ Refactor : μ±ν μ½μ μ²λ¦¬ κ°μ
β»οΈ Refactor : μ±ν URL μμ
β»οΈ Refactor : λ§μΆ€ν μΆμ² μλ² μλ μ μ¬μμ² λ§λλ‘ κ°μ
π§ Fix : μμ½ μκ° μ ν, κ²μ μμ² λ²κ·Έ κ°μ
ποΈ Chore : μλ² νμΈμ μν log μΆκ°
π οΈ μλΉμ€ μμ½
π μꡬ μ¬νκ³Ό ꡬν λ΄μ©
μ΅μ±λ Ή
μ΄μμ
μ‘°νμ§
π μμ νν©
νλ‘ νΈμΈ‘ νμ΄μ§λ³ λ§ν¬μ μ λ§λ¬΄λ¦¬λ μνμ λλ€. api μ°κ²°νλ©΄μ μμν λΆλΆλ§ μμ μ΄ μ΄λ£¨μ΄μ§λ©΄ λ κ² κ°μ΅λλ€.
λ°±μλμͺ½ μλ² λ°°ν¬κ° λΌμ νμ¬ κ°λ₯ν apiλ₯Ό μ°κ²°ν΄μ ν μ€νΈν΄λ³΄κ³ μμ΅λλ€. λ€λ§ μλ²½ν λ°°ν¬λ κ²μ μλμ΄μ ν μ€νΈμ μκ°μ΄ μ‘°κΈ μμλκ³ μμ΅λλ€.
μ§κΈ ν μ€νΈν΄λ³΄κ³ μλ νμ΄μ§λ
μ λμ λλ€. μ€κ°λ°νλκΉμ§ ν΄λΉ νμ΄μ§λ€μ μ΅λν ꡬνμ λλΌ μμ μ λλ€.
π¬ 리뷰 μμ μ°Έκ³ μ¬ν
npm install
μ€ννμ μΌ ν©λλ€.π¬Β μμ λ΄μμ λν μ€λͺ & κΆκΈν μ
μ§λ νλ‘μ νΈμμ axios κ΄λ ¨ μ½λλ₯Ό μμ±ν λ, λμΌν μν μ νλ μΈμ€ν΄μ€λ₯Ό κ°κ° λ°λ‘ λ§λ€κ³ μΈμ¦μ΄ νμν μμ²μλ ν€λμ ν ν° λ£λ μ½λλ₯Ό λ§€λ² μμ±νλ λ± μ€λ³΅ μ½λκ° λ§μ΄ λ°μνμ΅λλ€. κ·Έλμ μ΄λ²μ λͺ¨λνλ₯Ό μλν΄λ΄€μ΅λλ€!
AxiosInstanceλ₯Ό μμ±νμ¬ κ°μ΄ μ¬μ©
μΈμ¦μ΄ νμ μλ κ²½μ°μλ
defaultInstance
λ₯Ό μ¬μ©νκ³ , μΈμ¦μ΄ νμν κ²½μ°μauthInstance
λ₯Ό μ¬μ©ν©λλ€.authInstance
μλ΅ μΈν°μ ν°μμλ 401μλ¬κ° λ°μν κ²½μ° refresh ν ν°μ κ°±μ νλ μμ²μ 보λ λλ€.refresh ν ν°μ΄ μ ν¨νλ©΄(200) μλ‘ λ°κΈλ accessTokenμ λ€μ localStorageμ μ μ₯νκ³ κΈ°μ‘΄ μμ²μ μ¬μλν©λλ€.
μ ν¨νμ§ μμΌλ©΄ λ‘κ·Έμμ μ²λ¦¬λ©λλ€.
authInstance
μμ² μΈν°μ ν°μλ μμ²μ΄ μλ²μ μ μ‘λκΈ° μ μ accessTokenμ κ°μ Έμ ν€λμ μΈμ¦ κΆνμ μΆκ°ν©λλ€. μ΄ν μμ λ configλ₯Ό λ°νν΄μ μ΄ μ€μ λλ‘ μμ²μ΄ μ΄λ£¨μ΄μ§λλ€.errorInterceptor
λ₯Ό ν΅ν΄ μλ¬λ₯Ό μ½κ² κ΄λ¦¬ν μ μλλ‘ νμ΅λλ€. api μμ² ν¨μμ μ ν try-catchλ¬Έμ μλ΅ν μ μμ΄ api ν¨μλ κ°κ²°νκ² μμ±ν μ μμμ΅λλ€.κ°μΈμ μΌλ‘ κΆκΈν μ
dataκ° undefinedμΌ μ μλ€λ μλ¬κ° λ° λ κ·Έκ±Έ μ΄λ€ μμΌλ‘ ν΄κ²°ν΄μΌ μ’μμ§ μ λͺ¨λ₯΄κ² μ΅λλ€.
μ λ HostInfo.tsx, UserInfo.tsx λ±μ νμΌμμ undefinedμΌ μ μλ€λ μλ¬κ° λ°μνλ©΄ 첫 λ²μ§Έ λ°©μμ΄λ μΈ λ²μ§Έ λ°©μμ μ£Όλ‘ μ¬μ©νμ΅λλ€.
undefinedλ₯Ό μ κ±°ν μ μλ λ°©λ²μ΄ μ¬λ¬ κ°κ° μλλ° μ΄λ€ λ°©μμ΄ κ°μ₯ μ μ νμ§,
κ·Έλ¦¬κ³ undefinedλ₯Ό λ³΄ν΅ μ»΄ν¬λνΈμͺ½μμ κ±Έλ¬λ΄λμ§, μλλ©΄ api ν¨μλ hookμμ λ¨Όμ κ±Έλ¬λΈ λ°μ΄ν°λ₯Ό μ»΄ν¬λνΈμμ μ¬μ©νλμ§ κΆκΈν©λλ€!
API ν΅μ μ, μλ²μμ 보λ΄μ£Όλ μλ¬ status codeλ₯Ό κΈ°λ°μΌλ‘ errorInterceptorμμ λΆλ¦¬ν΄ μ²λ¦¬νκ³ μλλ°, κ° μλ¬λ₯Ό μ΄λ»κ² μ²λ¦¬ν΄μΌ ν μ§ μμ§ κ°μ΄ μ μ΅λλ€. 곡ν΅μΌλ‘ μλ¬ μ²λ¦¬λ₯Ό ν λ²μ νλ λ°©μμ΄ λμμ§, μλλ©΄ κ° API μμ² λ‘μ§μμ μλ¬λ₯Ό κ°λ³μ μΌλ‘ νΈλ€λ§νλ κ² λ λμ λ°©λ²μΈμ§ κ³ λ―Όμ λλ€!