-
Notifications
You must be signed in to change notification settings - Fork 13
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
[7주차] 러비 미션 제출합니다. #5
base: master
Are you sure you want to change the base?
Conversation
Refactor/#13/sign
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.
마지막 과제 고생하셨습니다!
react-query
와 àxios`를 사용해 api 로직을 깔끔하게 작성해주셔서 보기 편했습니다.
리팩토링을 하게된다면 UX적인 고민을 조금 더 해보면 좋을 거 같아요!
"@pages/*": ["pages/*"], | ||
"@styles/*": ["styles/*"], | ||
"@utils/*": ["utils/*"], | ||
"@type/*": ["type/*"] |
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.
다른 것들은 alias가 잘 적용되고 있는데 type은 제대로 적용되고 있지 않은 거 같아요. 폴더 명에 맞게 "@_types/*": ["types/*"]
같은 방식으로 수정하면 될 거 같아요!
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", |
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.
비트!
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.
@songess 왜케 깜찍하게 말하세요
{ index: true, element: <SignIn /> }, | ||
{ path: "/signin", element: <SignIn /> }, |
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.
동일한 컴포넌트를 보여주는데 같은 URL로 해도 될 거 같아요!
export const Router = createBrowserRouter([ | ||
{ | ||
path: "/", | ||
element: <Interceptors />, |
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.
로직을 완벽하게 이해한게 아니라 조심스럽지만, 현재 <Interceptors />
가 모든 컴포넌트의 요청을 인터셉트하고 있어서 토큰이 필요없는 로그인/회원가입 요청 시에도 토큰이 담기고 있는 거 같아요. axios의 인스턴스를 만들때 두 개의 인스턴스를 만들어서 하나는 인터셉터를 통해 토큰을 담아 보내는 인스턴스, 다른 하나는 토큰을 담지 않고 보내는 인스턴스를 사용해 해결할 수 있을 거 같아요.
<QueryClientProvider client={queryClient}> | ||
<Suspense fallback="..loading"> | ||
<ThemeProvider theme={theme}> | ||
<RouterProvider router={Router} /> |
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.
router를 사용해 훨씬 깔끔한 코드가 된 거 같아요.
import ResultsOnlyLayout from "@components/layout/ResultsOnlyLayout"; | ||
import Demo from "@components/result/Demo"; | ||
|
||
export default function ResultsOnlyDemo() { |
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.
뒤로가기버튼이 있었으면 좋을 거 같아요.
setSelected(selected === itemKey ? null : itemKey); | ||
}; | ||
|
||
const handleSubmit = () => { |
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.
투표에 성공하면 알림이 있었으면 좋았을 거 같고, 자연스레 결과페이지로 이동하면 좋을 거 같습니다.
<VoteBtn | ||
text="결과보기" | ||
onClick={() => navigate(`/result/${type}`)} | ||
/> |
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.
결과보기 버튼의 색상이 disabled
가 되어 비활성화된 버튼처럼 보여서 좀 더 짙은 색 혹은 다른 색을 사용해도 좋을 거 같아요.
$disableClick={true}> | ||
<TextWrapper> | ||
<LeftTextWrapper> | ||
<RankBox>{index + 1}</RankBox> |
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.
공동순위에 대한 로직도 있었으면 좋았을 거 같아요.
import { AxiosResponse } from "axios"; | ||
import { customAxios } from "./customAxios"; | ||
|
||
export interface VotingOptionDtoTypes { |
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.
src/api
에 선언되어있는 type
들은 types
안으로 몰아주면 좋을 거 같아요!
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와 vite를 이용한 프로젝트였다는 점에서 인상 깊었습니다!
이번 과제도 고생 많으셨습니다
"styled-components": "^6.1.11", | ||
"styled-reset": "^4.5.2" |
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.
스타일링을 위해서 styled-components 라이브러리를 사용하셨네요! 제가 지난 번에 전체 톡방에 올린 것처럼, reactJS 와 같이 CSR을 지원하는 라이브러리에서는 충분하지만 nextJS와 같이 서버 사이드 렌더링을 지원하는 경우 사용자가 네트워크가 느린 환경에서 애플리케이션을 이용할 경우 스타일링이 적용되지 않은 레이아웃만을 볼 가능성이 있어요!
앞으로도 다양한 기술 스택 선정을 해보시면 좋을 것 같습니다.
"eslint-plugin-react-refresh": "^0.4.6", | ||
"prettier": "^3.2.5", | ||
"typescript": "^5.2.2", | ||
"vite": "^5.2.0", |
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.
vite 번들러를 이용해서 리액트 프로젝트를 진행하셨네요. 기존의 cra은 내부적으로 webpack을 이용하여 번들링을 진행하는 반면에 vite는 esbuild라는 GO 언어를 통해 작성된 번들러를 개발환경에서, 배포환경에서는 rollup 을 이용해 HMR까지 지원하고 있다고 해요!
속도가 빠른 vite를 선택하신 점 좋아보여요! 다만 vite가 global 변수를 이용하려면 추가 설정을 해주어야 하고 webAPI의 사용이 일부 제한 된다는 점을 조심해서 사용하시면 좋을 것 같습니다.
function App() { | ||
return ( | ||
<QueryClientProvider client={queryClient}> | ||
<Suspense fallback="..loading"> |
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.
음... Suspense 컴포넌트를 이용해서 데이터를 로딩 중일 때 대체 UI를 보여주려면 fallback 속성에는 컴포넌트가 들어와야 하는 것 아닌가요?
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.
그냥 "..loading" 텍스트가 뜨게끔 해두었습니다..
export function setCookie( | ||
name: string, | ||
value: string | undefined, | ||
options: object, | ||
) { | ||
return cookies.set(name, value, { ...options }); | ||
} | ||
|
||
export function getCookie(name: string) { | ||
return cookies.get(name); | ||
} | ||
|
||
export function removeCookie(name: string) { | ||
return cookies.remove(name); | ||
} |
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.
cookie에 여러 값들을 저장하고, 조회하고, 삭제하는 기능의 함수들을 만들어주셨네요! 다만 이는 백엔드 단의 로직에서 set-cookie
헤더를 통해 요청을 response로 받는 시점에 바로 넣어줄 수도 있어요. 뿐만 아니라 httpOnly
같은 속성을 이용하면 Javascript로 cookie에 접근하는 것도 막아줄 수 있고요!
document.cookie
처럼 JS를 통해 쿠키에 바로 접근할 수 있게 만든다면 보안적으로 이슈가 될 수도 있다고 생각합니다.
baseURL: `${import.meta.env.VITE_APP_BASE_URL}`, | ||
headers: { | ||
"Content-Type": "application/json", | ||
"Access-Control-Allow-Origin": "*", |
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.
해당 헤더는 preflight 요청 시에 본 요청에 대한 cors 이슈를 해결해주기 위해서 백엔드 단에서 진행하는 헤더 설정으로 알고 있어요! 프론트엔드의 출처(프로토콜 + 도메인 주소 + 포트번호)를 여기에 명시해주면, 해당 출처로부터의 요청은 브라우저가 막지 않게 되는 것이지요!
따라서 러비 팀에서 axios 인스턴스를 만들어 쓸때 필요한 설정이 아니라 백엔드에서 진행해야 하는 것으로 생각하는데, 이렇게 코드를 작성하신 이유가 따로 있나 궁금합니다.
name: string; | ||
} | ||
|
||
export interface ResponseTypes { |
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.
getFinalResults.ts 파일에서도 동일한 이름의 인터페이스가 있는데, 좀더 semantic하게 작명하시면 좋을 것 같습니다.
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.
저는 한 파일에 하나의 api 함수만 존재한다면 여기서 type 정의 후 사용해도 괜찮을 거 같아요~ 다만 어떤 api들에 대한 type은 분리되어 있어서 파트너 간 정한 규칙으로 일관성을 유지했다면 더 좋을 거 같네요. 그리고 승완님 말씀처럼 타입을 export 해서 다른 파일에서도 사용한다면 일반적인 네이밍보다는 특수한 네이밍이 나을 거 같아요. GetTopicByIdRes 이런 식으로요~
|
||
export async function postEmail(props: postEmailTypes) { | ||
const { email } = props; | ||
const response = await customAxios.post("/api/users/verify", { |
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.
이메일 인증 방식까지 거치신 부분 너무 좋습니다 👍
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.
맞아요 최고입니다....👍
import { useLayoutEffect } from "react"; | ||
|
||
export default function useSetInterceptors() { | ||
useLayoutEffect(() => { |
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.
useLayoutEffect 훅을 이용하여 렌더링하여 계산된 DOM 결과가 화면에 paint 되기 전에 실행하고 싶은 동작을 잘 구현하신 것 같습니다. 훅의 유연한 사용 좋습니다 👍
(request) => { | ||
const accessToken = getCookie("accessToken"); | ||
if (accessToken) { | ||
request.headers.Authorization = accessToken; |
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.
쿠키에 저장한 accessToken을 요청의 헤더에 싣어 전송하고 있는데, 이는 어찌보면 불필요한 동작이 아닐까 싶거든요. 쿠키의 속성에 domain과 path 를 명시해 놓는다면, 해당 주소로 전송되는 모든 요청에는 자동으로 쿠키가 들어가는 것으로 알고 있습니다.
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.
@Programming-Seungwan 승완님이 말씀하신 건 토큰을 set-cookie 헤더에 담아서 클라이언트에게 보내주는 경우를 의미하는 것 같아요. 이 경우에는 로그인 이후에 클라이언트에서 토큰에 대한 아무런 코드를 쓰지 않아도 리퀘스트를 보내기만 하면 인증서가 Cookie 헤더에 함께 담겨서 전송되죠.
그런데 러비 팀에서는 로그인 시 서버에서 액세스토큰을 바디에 담아 응답값으로 넘겨주고 클라이언트에서는 그 토큰을 쿠키라는 저장 공간에 담아서 사용하고 있는 거 같아요. 따라서 서버가 set-cookie 헤더에 인증서를 추가해서 넘겨주는 게 아니기 때문에 서버에 요청을 보낼 때마다 자동으로 인증서가 전달되는 게 아닌 거죠.
그래서 임의적으로 클라이언트가 별도의 코드를 작성해서 쿠키에 담아둔 토큰을 가져다가 넣어주는 작업이 필요해요. 마치 그냥 로컬스토리지에 토큰을 저장하는 것처럼 러비팀에서는 쿠키에 토큰을 저장했다고 생각하시면 될 거 같아요!
import { getFinalResult } from "@api/getFinalResult"; | ||
import { ResponseTypes } from "@api/getFinalResult"; |
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.
한 코드로 넣어주시면 좋을 것 같아요
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.
고생하셨습니다~ 남겨드린 리뷰 참고해주세요!
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", |
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.
@songess 왜케 깜찍하게 말하세요
name: string; | ||
} | ||
|
||
export interface ResponseTypes { |
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.
저는 한 파일에 하나의 api 함수만 존재한다면 여기서 type 정의 후 사용해도 괜찮을 거 같아요~ 다만 어떤 api들에 대한 type은 분리되어 있어서 파트너 간 정한 규칙으로 일관성을 유지했다면 더 좋을 거 같네요. 그리고 승완님 말씀처럼 타입을 export 해서 다른 파일에서도 사용한다면 일반적인 네이밍보다는 특수한 네이밍이 나을 거 같아요. GetTopicByIdRes 이런 식으로요~
(request) => { | ||
const accessToken = getCookie("accessToken"); | ||
if (accessToken) { | ||
request.headers.Authorization = accessToken; |
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.
@Programming-Seungwan 승완님이 말씀하신 건 토큰을 set-cookie 헤더에 담아서 클라이언트에게 보내주는 경우를 의미하는 것 같아요. 이 경우에는 로그인 이후에 클라이언트에서 토큰에 대한 아무런 코드를 쓰지 않아도 리퀘스트를 보내기만 하면 인증서가 Cookie 헤더에 함께 담겨서 전송되죠.
그런데 러비 팀에서는 로그인 시 서버에서 액세스토큰을 바디에 담아 응답값으로 넘겨주고 클라이언트에서는 그 토큰을 쿠키라는 저장 공간에 담아서 사용하고 있는 거 같아요. 따라서 서버가 set-cookie 헤더에 인증서를 추가해서 넘겨주는 게 아니기 때문에 서버에 요청을 보낼 때마다 자동으로 인증서가 전달되는 게 아닌 거죠.
그래서 임의적으로 클라이언트가 별도의 코드를 작성해서 쿠키에 담아둔 토큰을 가져다가 넣어주는 작업이 필요해요. 마치 그냥 로컬스토리지에 토큰을 저장하는 것처럼 러비팀에서는 쿠키에 토큰을 저장했다고 생각하시면 될 거 같아요!
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.
혹시 이 부분을 따로 커스텀 훅으로 정의해서 사용하신 이유가 있나요? useLayoutEffect를 사용하신 이유도 궁금합니다. 왜냐면 제가 알기로는 이런 인스턴스를 정의하고 그 아래에 바로 customAxios.interceptors.request.use라는 코드를 작성하면 언제나 해당 인스턴스가 실행될 때마다 인터셉터가 동작하는 걸로 알고있거든요. 쿠키를 사용하면 뭔가 다른 걸까요..?
export default function SignUp() { | ||
const [name, setName] = useState(""); | ||
const [ID, setId] = useState(""); | ||
const [PW, setPw] = useState(""); | ||
const [pwCheck, setPwCheck] = useState(""); | ||
const [email, setEmail] = useState(""); | ||
const [emailCheck, setEmailCheck] = useState(""); | ||
const [selectedTeam, setSelectedTeam] = useState(""); | ||
const [selectedPart, setSelectedPart] = useState(""); | ||
const [isEmailClicked, setIsEmailClicked] = useState(false); | ||
const [verificationCode, setVerificationCode] = useState(""); | ||
|
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.
모든 input 컴포넌트를 제어 컴포넌트로 동작하게 만들면 하나의 인풋값이 바뀔 때마다 페이지 전체가 리렌더링 되는 현상이 발생하게 돼요! 그래서 이걸 방지하기 위해 react-hook-form 같은 라이브러리를 사용하기도 합니다. 참고하시면 좋을 거 같아요 ~ 리액트 훅폼
<InputContainer name="PASSWORD" state={PW} setState={setPw} /> | ||
<InputContainer custom={true}> | ||
<label>CONFIRM PASSWORD</label> | ||
<div> | ||
<input | ||
type="text" | ||
id="pwCheck" | ||
placeholder="CONFIRM PASSWORD" | ||
value={pwCheck} | ||
onChange={(e) => setPwCheck(e.target.value)} | ||
/> |
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.
이메일 인증 | ||
</VerifyBtn> | ||
</div> | ||
</InputContainer> | ||
{isEmailClicked && ( | ||
<InputContainer custom={true}> | ||
<label>VERIFY EMAIL</label> | ||
<div> | ||
<input | ||
type="text" | ||
id="emailCheck" | ||
placeholder="이메일 인증코드" | ||
value={emailCheck} | ||
onChange={(e) => setEmailCheck(e.target.value)} | ||
/> | ||
<EmailVerifyText $emailVerify={emailVerify}> | ||
인증코드 확인 | ||
</EmailVerifyText> | ||
</div> | ||
</InputContainer> | ||
)} | ||
</FilledInput> |
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.
버튼은 주로 클릭하는 용도로 사용하는데, PW 확인이나 인증코드 확인이라고 적힌 버튼은 클릭하는 용도가 아니라 유효성 검증에 대한 것들이라 다른 식으로 표현했다면 더 좋았을 거 같아요~
<VoteWrapper onClick={() => navigate("/results-only/front")}> | ||
FE 파트장 투표결과 | ||
<br /> | ||
바로가기 | ||
</VoteWrapper> | ||
<VoteWrapper onClick={() => navigate("/results-only/back")}> | ||
BE 파트장 투표결과 | ||
<br /> | ||
바로가기 | ||
</VoteWrapper> | ||
<VoteWrapper onClick={() => navigate("/results-only/demo")}> | ||
데모데이 투표결과 |
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.
버튼 태그에서 다른 로직 없이 이동만 하고 있기 때문에 useNavigate로 페이지를 이동하는 것보다 Link 태그를 사용하는 게 더 좋을 거 같아요! 그리고 Wrapper라는 네이밍만으로는 이게 버튼 태그인지 알기 어려워보입니다 ㅠㅠ
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.
vote/src/hooks/useGetTopicsById.ts
Outdated
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.
getTopicById랑 getFinalResult랑 getVotingOptionById가 이름만 봐서는 어떤 역할을 하는 함수인지 잘 모르겠어요. 파트장 투표 결과를 가져오는 건지 아니면 데모데이 투표 결과를 가져오는 건지 잘 모르겠네요. 유지 보수 시에 다른 사람들도 쉽게 이해할 수 있는 네이밍이면 좋을 거 같습니다!
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.
마지막 과제 수고하셨습니다!! PR 자세하게 작성해주셔서 코드 읽는데 편했습니다 ㅎㅎ
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.
이메일 인증 실패 시 콘솔에만 에러 메세지가 뜨고 있어서, alert 창 등을 통해 사용자에게 실패 여부를 알려주면 UX 개선에 도움될 것 같아요!
|
||
export async function postEmail(props: postEmailTypes) { | ||
const { email } = props; | ||
const response = await customAxios.post("/api/users/verify", { |
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.
맞아요 최고입니다....👍
const EmailVerifyText = styled.p<{ $emailVerify: boolean }>` | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: 100%; | ||
height: 3.625rem; | ||
background-color: ${({ $emailVerify, theme }) => | ||
$emailVerify ? theme.colors.active : theme.colors.confirm}; | ||
color: ${({ theme }) => theme.colors.black}; | ||
${({ theme }) => theme.fonts.SignBtnText}; | ||
`; |
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.
cursor:pointer를 넣어주면 더 좋을 것 같아요!
<Section> | ||
<FilledInput> | ||
<InputContainer name="ID" state={ID} setState={setId} /> | ||
<InputContainer name="PASSWORD" state={PW} setState={setPw} /> |
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.
맞아요! 회원가입 때도 마찬가지로 안 보이도록 처리해주시면 좋을 것 같아요
<> | ||
<VoteHeader /> | ||
<>{children}</> | ||
<VoteBtn text="돌아가기" onClick={() => navigate("/vote/main")} /> |
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.
돌아가기 버튼을 눌렀을 때 메인페이지로 가는 것보다는 이전 페이지로 돌아가는게 더 좋을 것 같아요! 투표하기 전에 결과를 본 후에 다시 투표를 하는 상황 등을 고려해서요!
<VoteHeader /> | ||
<CenterWrapper> | ||
<HeaderText>파트장 투표</HeaderText> | ||
<SubTitleText>* 본인 파트 + 한 번 투표할 수 있습니다</SubTitleText> |
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.
사소하고 개인적인 의견이지만 + 기호보다는 "본인 파트만 한 번 투표할 수 있습니다." 등의 텍스트로 바꾸면 좋을 것 같아요!
<HeaderText> | ||
{type === "front" ? "FE 파트장 투표" : "BE 파트장 투표"} | ||
</HeaderText> |
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.
HeaderText가 VotePart 페이지에서만 쓰인다면 좋은 로직 같지만 다른 페이지에서도 계속 반복되고 있는 것 같아요! 아예 Header 공통 컴포넌트를 따로 만드는게 더 좋을 것 같습니다!
vote/src/styles/theme.ts
Outdated
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.
theme 적극 활용하신 점 너무 좋습니다!!
vote/src/types/postEmailTypes.ts
Outdated
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.
타입들을 따로 빼서 정의해주니 코드 가독성이 좋네요!
배포링크
5주차 미션: React-Vote
🌱 필수 구현
🪴 Team
👨💻 팀원 정보 및 역할
👨💻 팀원 역할 분담
POST
GET
GET, POST
📄 서버 통신
🔻 러비팀은 Next가 아닌 React+typescript로 구현하기로해서 이번 투표 과제는 CSR로 구현했습니다.
1. axios
사실 자바스크립트에는 fetch api가 존재하지만, 더 많은 기능을 지원하기에 개발자들은 서버 통신에 보다 편리한 axios를 선호한다고..!
2. async/await
왜 promise가 아닌 async, await를 사용했는가?
⚠️ Promise & then 의 문제점
👉 그래서 등장한 async/await
3. customAxios
4. 인터셉터
👉 authorization header
5. react-query와 custom-hook
🔻 GET - useQuery 사용
🔻 POST - useMutation 사용
👉 왜 React Query를 사용했는가?
6. 우리가 리액트쿼리를 적용한 방법
📦api
┣ 📜cookie.ts
┣ 📜customAxios.ts
┣ 📜getVotingOptionsById.ts
┗ 📜postSignIn.ts
📦hooks
┣ 📜useGetVotingOptionsById.ts
┗ 📜usePostSignIn.ts
🎀 참고
🔗 리액트쿼리 공식문서.