Conversation
minjeoong
left a comment
There was a problem hiding this comment.
코드가 읽기 편하고 전체적으로 깔끔하게 잘 짜신 것 같아요 !
4주차도 너무 고생 많으셨습니다 :) 👍🏻👏🏻👏🏻
| try { | ||
| const response = await axios.post( | ||
| `${import.meta.env.VITE_BASE_URL}/login`, | ||
| { | ||
| username, | ||
| password, | ||
| } | ||
| ); |
There was a problem hiding this comment.
p3: axios 환경 세팅으로 BASE_URL 을 지정할 수 있어요!
사용하는 배포된 api url 가 2개 이상이라면 이렇게 지정하면 안되겠지만,
지금처럼 한개의 api만 사용한다면 이렇게 지정해두는 방법도 있습니다!
/login 등 api 경로를 다른 파일로 나눠둔 후 axios.get(PATH_API.MY_HOBBY) 이런식으로 부르는 방법도 참고해주시면 좋을 것 같아요 !! 🍀
const api = axios.create({
baseURL: PATH_API.API_DOMAIN,
headers: {
'Content-Type': 'application/json',
},
// withCredentials:true, // 쿠키 cors 통신 설정
});| background: ${({ theme }) => theme.colors.gray2}; | ||
| color: ${({ theme }) => theme.colors.white1}; | ||
| border: none; | ||
| border-radius: 5px; |
There was a problem hiding this comment.
p4: 보통 border-radius 는 px 로 처리하시는지 궁금합니다 !
There was a problem hiding this comment.
p4: 보통 border-radius 는 px 로 처리하시는지 궁금합니다 !
넵 저는 border 관련 스타일링은 px로 처리합니다! 해당 내용은 민정님 코리에 달았으니 참고 부탁드려요.
| `${import.meta.env.VITE_BASE_URL}/user/my-hobby`, | ||
| { | ||
| headers: { | ||
| token: token, | ||
| }, | ||
| } | ||
| ); |
There was a problem hiding this comment.
p2: 헤더 세팅도 마찬가지로 axios 환경 세팅에서 가능합니다
매번 세팅하는 것보다 유지보수성 차원에서 좋을 것 같은데, 시간없어서 이렇게 하신 거라면 백번 이해합니다...........🥺🥺
api.interceptors.request.use(
(config) => {
// 요청 전 처리
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.token = `${token}`;
}
return config;
},
(error) => Promise.reject(error),
);
| {funnel.currentStep === "name" && ( | ||
| <NameStep name={name} setName={setName} onNext={funnel.next} /> | ||
| )} |
There was a problem hiding this comment.
p5: 오 funnel 은 처음 보는데, .next 하면 다음 스텝으로 넘어가는 시스템인가보네요!
저도 step 부분에서 코드가 더러워질 것 같아서 고민을 좀 했었는데,
이런 방식으로 짜면 좀 더 깔끔해질 것 같네요 !! 배워갑니다..👏🏻
| function useFunnel<T>(steps: T[]): UseFunnelReturnType<T> { | ||
| const [currentIndex, setCurrentIndex] = useState(0); | ||
|
|
||
| const next = () => { | ||
| setCurrentIndex((prevIndex) => | ||
| prevIndex < steps.length - 1 ? prevIndex + 1 : prevIndex | ||
| ); | ||
| }; | ||
|
|
||
| const prev = () => { | ||
| setCurrentIndex((prevIndex) => (prevIndex > 0 ? prevIndex - 1 : prevIndex)); | ||
| }; | ||
|
|
||
| const reset = () => { | ||
| setCurrentIndex(0); | ||
| }; |
heesunee
left a comment
There was a problem hiding this comment.
역시 리드다운 코드군요 .. 👍 딱히 집히는 부분 없이 술술.. 술술 읽혔습니다 ...
사실 스타일링 하다보면 컴포넌트 이름을 대충 짓게 될 때도 있는데 네이밍이나 funnel, 또 pr에 남겨주신 dispatch 부분들도 코드를 보면서 이런식으로 사용하는 구나 - 하고 많이 배워가는 것 같아요. 민정님 의견처럼 api 관련 부분만 리팩토링하면 더 좋을 것 같습니다! 사실 웹 1팀 중에 마지막으로 코드 리뷰를 진행했는데 다른 분들 코드에 남겨주신 코멘트랑, 건휘님 코드 보고 합세 코드 빨리 고쳐야겠다고 생각중입니다.. 하하 ㅎㅎㅎ 4주차 과제 너무 고생많으셨습니다!!
| <NavItem | ||
| active={activeTab === "hobby"} | ||
| onClick={() => setActiveTab("hobby")} | ||
| > | ||
| 취미 | ||
| </NavItem> | ||
| <NavItem | ||
| active={activeTab === "info"} | ||
| onClick={() => setActiveTab("info")} | ||
| > | ||
| 내 정보 | ||
| </NavItem> |
There was a problem hiding this comment.
p5: 저는 취미랑 정보페이지를 따로 만들어서 이동하게 했는데, 이렇게도 구현할 수 있었네요. 배워갑니다!
| const NavItem = styled.span<{ active: boolean }>` | ||
| font-size: 1.2rem; | ||
| cursor: pointer; | ||
| color: ${({ active }) => (active ? "white" : "#ddd")}; |
There was a problem hiding this comment.
p4: 저는 active 상태에 따라 다른 색을 보여주고 싶을때, inactive, active로 이름을 붙여서 사용하는 편인데 참고해주셔도 좋을것 같아요! 여기 하나는 #ddd로 색상이 정의되어있어서 제안드립니다 ㅎㅎ
| function useFunnel<T>(steps: T[]): UseFunnelReturnType<T> { | ||
| const [currentIndex, setCurrentIndex] = useState(0); | ||
|
|
||
| const next = () => { | ||
| setCurrentIndex((prevIndex) => | ||
| prevIndex < steps.length - 1 ? prevIndex + 1 : prevIndex | ||
| ); | ||
| }; | ||
|
|
||
| const prev = () => { | ||
| setCurrentIndex((prevIndex) => (prevIndex > 0 ? prevIndex - 1 : prevIndex)); | ||
| }; | ||
|
|
||
| const reset = () => { | ||
| setCurrentIndex(0); | ||
| }; |
| type="text" | ||
| placeholder="아이디" | ||
| value={username} | ||
| onChange={(e) => setUsername(e.target.value)} |
There was a problem hiding this comment.
p5:코리 달다보니까 여기에 'e'가 'any' 타입이라고 하는데요! 사실 제가 해결방법을 몰라서... 찾아보니 들어오는 값에 따라서 e:string 하면 해결될 것 같다는 생각이 드네요!!
| const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
| const value = event.target.value; | ||
| setName(value); | ||
|
|
||
| if (value.length > 8) { | ||
| setNameError("이름은 8글자 이하로 입력해주세요"); | ||
| } else { | ||
| setNameError(""); | ||
| } |
There was a problem hiding this comment.
p5: 제가 에러 구현을 못해내서(사실 컴포넌트 내에 props로 받아서 출력하도록 만들어뒀거든요) 여기 많이 참고하겠습니다!!! 배워갑니다👍
ayla-12
left a comment
There was a problem hiding this comment.
안녕하세요 건휘님!! 처음보는 퍼널 같은거 보면서 이런식으로도 구현가능하구나~ 깨닫는 코드 리뷰시간이었던 것 같습니다. 역시 리드... 합세 잘 부탁드려요!!!
✨ 구현 기능 명세
💡 기본 과제
취미,내 정보메뉴 탭로그아웃버튼취미,내 정보취미 페이지, 내 정보 페이지 출력 (1개의 페이지로 구현해도 되고, url 달라도 됨)🔥 심화 과제
공유과제
제목: Typescript 왜 사용할까? 제대로 알고 사용하자
링크 첨부 : https://wave-web.tistory.com/121
❗️ 내가 새로 알게 된 점
React.Dispatch<React.SetStateAction>
상위 컴포넌트에서 useState 상태 변경 함수를 넘길 때 () => void 로 넘겼었는데 정의 할 수 있는 타입이 있었다.
Dispatch와setStateAction을 통해setState함수의 타입을 정의해주면 상태 업데이트 필요 시 요구하는 인자와 반환 값을 정확하게 명시할 수 있다.setState가 인자로 받고, 업데이트하게 될 값의 타입이string임을 쉽게 파악할 수 있다. 상태를 문자열로 설정하거나, 이전 문자열을 기반으로 새로운 문자열을 설정할 수 있도록 한다.해당 프로퍼티의 의미를 알기 쉽게 함으로써 가독성을 좋게 하고, 추후 유지보수에도 용이하다.
boolean인 경우에는React.Dispatch<React.SetStateAction<boolean>>을 사용한다.❓ 구현 과정에서의 어려웠던/고민했던 부분
퍼널 로직으로 회원가입 페이지 관리하기
이번 과제 중에서 가장 많은 시간을 투자하고, 고민을 많이했던 부분입니다.
회원가입 페이지의 흐름이 이름 => 비밀번호 => 취미 순서로 입력되어야 하고, 한 페이지에서 관리하다보니 누가봐도 더러운 코드에 가독성도 많이 떨어지는 것이 느껴졌습니다.
https://www.youtube.com/watch?v=NwLWX2RNVcw
전에 우연히
퍼널 설계관련한 영상을 본것이 기억이나, 해당 영상을 참고하여퍼널 설계를 바탕으로 회원가입 페이지를 구현하였습니다.useFunnel.ts
이 useFunnel 훅은 여러 단계로 이루어진 UI 흐름(회원가입 단계를 순차적으로 진행하는 폼)을 관리할 수 있도록 돕는 커스텀 훅입니다. 현재 단계, 다음 단계로의 이동, 이전 단계로의 이동 등을 쉽게 처리할 수 있습니다.
step값에 따라 각 컴포넌트 UI를 조건부 렌더링 할 수 있습니다.
각 컴포넌트에서 다음 버튼을 누를 때 step 상태를 원하는 컴포넌트로 업데이트 된다.
이 방식을 통해 step이 추가되어도 유연하게 대응할 수 있고, 전역 상태를 사용하지 않아도 되어 어떤 상태가 어떤 UI에서 수집되는지도 한눈에 볼 수 있게 된다. => 확장 가능성, 가독성 up
🥲 소요 시간
12h🖼️ 구현 결과물
회원가입
2024-11-12.3.51.46.mov
로그인, 취미 조회
2024-11-12.3.52.55.mov
마이페이지 - 내정보 수정
2024-11-12.3.55.03.mov