-
Notifications
You must be signed in to change notification settings - Fork 0
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
[ 4주차 기본/심화/공유 과제 ] 로그인, 회원가입 구현해보기 #4
base: main
Are you sure you want to change the base?
Conversation
api 에서 에러가 발생했을 때
|
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.
울 합세 리더님 ..💚 4주차 과제 너무 고생하셨습니다!!
컴포넌트 분리가 잘 되어있고 구조가 좋아서 읽기 쉽고 재밌었어요!!
타입 스크립트 prop 도 분리를 잘 해주셔서 감탄했어요
합세도 퐛팅해봅시다!! 🍀🍀
week4/src/pages/Login.tsx
Outdated
return ( | ||
<LoginModal> | ||
<LoginTitle>Login</LoginTitle> | ||
<LoginImage src={LoginImg} /> |
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.
img 태그에 alt 속성도 넣어주면 좋을 것 같아요 :>
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.
반영했습니다!! ㅎㅎ꼼꼼하게 봐주셔서 감사해요
week4/src/components/InputModule.tsx
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.
공통된 컴포넌트 이름까지 세심한 수정 ..!! 너무 좋아용
week4/src/components/ChangePwd.tsx
Outdated
margin-top: 3rem; | ||
margin-bottom: 3rem; |
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.
margin : 3rem 0 으로 작성하면 상하 마진을 3rem 으로 쉽게 작성할 수 있을 것 같아요 !
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.
더 간략하게 작성할 수 있네요~!! 반영했습니당!
week4/src/types.ts
Outdated
export interface JoinType { | ||
|
||
export interface infoType { | ||
authenticationId: string; | ||
password: string; | ||
nickname: string; | ||
phone: string; | ||
} | ||
|
||
export interface JoinType extends infoType { | ||
password: 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.
왜 password 만 따로 분리했는지가 궁금해요!
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.
JoinType
과 InfoType
에서 중복되는 타입들이 많아서,,
JoinType
는 InfoType
를 상속받아 추가되는 항목만 적어줬습니다!
week4/src/pages/Join.tsx
Outdated
/** 인풋 확인 */ | ||
const checkInput = () => { | ||
if (id === "") { | ||
alert("id를 입력하세요"); | ||
return false; | ||
} | ||
if (pwd === "") { | ||
alert("비밀번호를 입력하세요"); | ||
return false; | ||
} | ||
if (nickName === "") { | ||
alert("닉네임을 입력하세요"); | ||
return false; | ||
} | ||
if (phone === "") { | ||
alert("전화번호를 입력하세요"); | ||
return false; | ||
} | ||
return true; |
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.
저 조건들에 걸리면 서버에 요청을 보내지 않아서 응답이 안 오기 때문에 따로 작성했습니다!
const ToggleIcon = styled.img<{ $active: boolean }>` | ||
width: 1rem; | ||
height: 1rem; | ||
rotate: ${(props) => (props.$active ? "0deg" : "180deg")}; |
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.
토글 버튼이 180도 돌아가도록 구현했네요! 신기해요 >_<
const [id, setId] = useForm(""); | ||
const [pwd, setPwd] = useForm(""); | ||
const [nickName, setNickName] = useForm(""); | ||
const [phone, setPhone] = 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.
phone 은 useForm hook 을 쓰지 않은 이유가 있을까요?
다른 이유가 없다면 일관성을 유지를 위해 통일하는게 좋을것 같다는 생각이 듭니다 !!
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 에 입력될 때마다 setState
가 돌아가는 핸들러 함수가 있는데,
checkPhoneNo
함수로 정규표현식을 이용해 입력할 때마다 검사해야 했기 때문에 다르게 구현했습니다!
훅 내부 함수를 상황에 맞게 더 잘 사용할 수 있는 방법이 있을까요 ..?🥹
week4/src/apis/memberLogin.ts
Outdated
/** 로그인 */ | ||
export const memberLogin = async (props: LoginType) => { | ||
try { | ||
const res = await serverAxios.post("/member/login", props); | ||
return res; | ||
} catch (error) { | ||
if (isAxiosError(error)) alert(error.response?.data.message); | ||
else { | ||
console.log(error, "unknown error: memberLogin"); | ||
} | ||
return false; |
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.
성공 시 res를 그대로 반환하고 있고, 실패 시 false를 반환하네요!
일관성 있는 반환 타입을 유지하지 않아 보여서 항상 같은 형태의 객체를 반환하거나, 에러 상황에서 null을 반환하는 것은 어떨까요 ?? :)
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 axios from "axios"; | ||
|
||
export const serverAxios = axios.create({ | ||
baseURL: import.meta.env.VITE_SERVER_URL, | ||
}); |
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 통신 부분을 이렇게 빼서 구현할 수가 있군요..!
완전 큰 깨달음 얻고갑니다 .. 우와 🤩..
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.create
를 이용해 인스턴스를 만들어주면 편리하게 사용할 수 있답니다!!ㅎㅎ
const Join = () => { | ||
const navigate = useNavigate(); | ||
const [id, setId] = useForm(""); | ||
const [pwd, setPwd] = useForm(""); | ||
const [nickName, setNickName] = useForm(""); | ||
const [phone, setPhone] = useState(""); | ||
|
||
const idRef = useRef<HTMLInputElement>(null); | ||
const pwdRef = useRef<HTMLInputElement>(null); | ||
const nickNameRef = useRef<HTMLInputElement>(null); | ||
const phoneRef = useRef<HTMLInputElement>(null); | ||
|
||
const onPhoneChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setPhone(checkPhoneNo(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.
useForm과 비교해보니 onPhoneChange엔 checkPhoneNo() 과정이 포함되어 있더라고요!
이 차이 때문에 phone만 useState를 사용해서 이 파일에서 정리해준게 맞나요?! 제가 맞게 이해한건지 궁금합니다!
또, checkPhoneNo()는 심화과제에 있던 번호 형식 변경(000-0000-0000) 부분이 맞나요?!
컴포넌트 나누는 부분 진짜 완전 배워가요.. 이해 쏙쏙 정리 쏙쏙 .. 👍🏻👍🏻
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.
넵 정확히 보셨네요!!!
훅 안에 입력할 때마다 setState
를 이용해서 state
를 바꿔주는 부분이 있는데, 전화번호의 경우 입력할 때마다 정규표현식으로 체크해줘야 해서 따로 작성했습니다.!
훅을 최대한 이용하고 싶었는데 이건 따로 작성해주는 게 더 간단할 것 같아 일단 이렇게 작성했습니다,,ㅎㅎ
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 { | ||
ALERTMSG, | ||
BTNTXT, | ||
INFORMATION, | ||
INFORMMSG, | ||
} from "../constants/messages"; |
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.
상수 메시지..👍🏻 너무 꼼꼼해요 진촤
저도 합세 때 사용해서 꼭 습관 들이기를 .. 😊
<MainContainer> | ||
<ReactPlayer | ||
url={mainVideo} | ||
playing={true} | ||
loop={true} | ||
playbackRate={4} | ||
/> | ||
<BtnWrapper> | ||
<CommonBtn text={BTNTXT.myInfo} link={`/mypage/${memberId}`} /> | ||
<CommonBtn text={BTNTXT.join} link="/join" /> |
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.
이번에 합세 하면서도 assets 링크 같은거 그대로 넣지않고, 정리 후에 넣어주는 모습이 정말 배울 점이라고 생각했는데
개인 과제에서도 이렇게 해주시니 너무 이해가 쏙쏙 가네요..😊 진짜 깔끔한 코드가 이런거구나 완전 배워갑니다!
// 비밀번호 형식 검사 | ||
if (!verifyPwd(pwd)) { | ||
alert(ALERTMSG.pwdFormat); | ||
return false; |
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.
저는 비밀번호 형식이 맞지 않으면 400대 error가 뜨고, 서버에서 오는 message를 이용해서 alert 창을 띄웠는데
이렇게 구체적으로 형식 검사를 진행한 후 지정한 alert 창을 띄워주는 이 방식이 훨씬 구체적이고 더 꼼꼼한 방법 같습니다..
그치만 여기서 드는 초보의 궁금증 하나.. 이 두 방식의 큰 차이가 무엇인지 아시나요?!?
세심하게 오류를 다룰 수 있다는 점이 맞는건지 궁금합니다!!
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.
오..!! 그러네요?! 요청을 덜 보내게 되어 네트워크 부담이 덜한다.. 생각도 못했는데 너무 좋은 장점이네요 ㅎㅎ!!
좋은 설명 너무 감사합니다! 💝💝
const checkInput = () => { | ||
const fields = [ | ||
{ field: id, ref: idRef, msg: ALERTMSG.id }, | ||
{ field: pwd, ref: pwdRef, msg: ALERTMSG.pwd }, | ||
{ field: nickName, ref: nickNameRef, msg: ALERTMSG.nickName }, | ||
{ field: phone, ref: phoneRef, msg: ALERTMSG.phone }, | ||
]; | ||
|
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.
checkInput 함수의 동작을 설명해주실 수 있나요 ..! (궁금합니닷 완전 확실하게 배워가고 싶어요😀)
field가 비어있을 경우, alert 창을 띄워주고,
비밀번호 유효 검사에 통과하지 않을 경우, 비밀번호 포맷 관련 alert 창을 띄워주는 방식이 맞나요?!
또, warnRef와 resetRefWarn 이 어떻게 작동하는 지도 궁금합니다!
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 checkInput = () => {
if (id === "") {
alert("id를 입력하세요");
return false;
}
if (pwd === "") {
alert("비밀번호를 입력하세요");
return false;
}
if (nickName === "") {
alert("닉네임을 입력하세요");
return false;
}
if (phone === "") {
alert("전화번호를 입력하세요");
return false;
}
return true;
사실 위의 코드와 비슷한 동작을 하는데, 구조가 너무 반복되는 것 같아 줄여보겠다고 작성한 코드입니다!!
읽기에는 더 안 좋은 것 같네요 😅
체크해야 하는 필드를 fields로 만들어
field
는 검사 항목,
ref
에는 해당 항목을 입력하는 인풋,
msg
에는 해당 필드 미입력시 띄워줘야 하는 메시지를 담았습니다!
필드가 "" 와 같은 경우(비어있는 경우) 에러 메시지를 띄워주고, 해당 ref를 warn 합니다!
warnRef
와 resetRefWarn
는 인풋 비어있으면 인풋창 border 색 바꾸고 focus 처리 해주기 위해서 만든 건데,
/** focus, border 색 바꾸기 */
const warnRef = (ref: React.RefObject<HTMLInputElement>) => {
if (ref.current) {
ref.current.focus();
ref.current.style.setProperty("border-color", "red");
}
};
warnRef
는 ref 를 props로 받아서 ref(해당 필드의 인풋창이겠죠???) 가 유효하면 focus() 처리를 해주고 테두리 색을 red로 바꿔줍니다!!
/** focus 해제, border 색 복구 */
const resetRefWarn = (ref: React.RefObject<HTMLInputElement>) => {
if (ref.current !== null) {
ref.current.blur();
ref.current.style.setProperty("border-color", "black");
}
};
resetRefWarn
는 반대로 focus() 를 해제하는 blur() 함수를 사용했고, 테두리 색을 다시 검정색으로 복구합니다.
그래서 field가 ""가 아니라면 (비지 않았다면) resetRefWarn(ref);
를 실행해서 ref 상태를 복구시켜줍니다!~~
이해하시는 데에 도움이 되었으면 좋겠네요 !!
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.
와.. 승연님
당장 어디서든 강의 열어주시면 안되겠습니까 .. !!!
제가 아직 저렇게 요약된 코드를 풀어서 이해하는 부분이 살짝 부족해서 여쭤봤던 거고!! 설명을 너무 잘해주셔서 이해가 너무 잘 되네요.. 이 부분 저도 완전 연습해서 써먹고 싶어요 ㅎㅎ!
warnRef와 resetRefWarn 부분은 심화과제 내용이라 제가 모르고 있었나봐요! ㅎㅎ
승연님 자세한 설명 덕분에 요 부분까지 챙겨가서 너무 좋네요..
정성스러운 답변 너무너무 감사해요 💘 눈물 좔좔.. 나의 최고의 선생님..
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.
typescript에서 catch 내부error 타입이 unknown이기 때문에 axios error인지 타입을 확인하고 그 타입이 맞을시 error객체에 접근해야합니다! 즉 타입가드를 사용한건데요.
이부분에 대해선 아래 링크 한번 보시면 도움이 될 것 같습니다
https://gxxrxn.github.io/axios-error-type-guard/
|
✨ 구현 기능 명세
🧩 기본 과제
로그인 페이지
회원가입 페이지
마이페이지
🔥 심화 과제
메인페이지
로그인 페이지
회원가입 페이지
input이 비어있는 상태로 api연결 시도했을시
해당 input 테두리 색상 변경
input에 focus 맞추기
api요청 금지
전화번호 양식 정규표현식으로 자동입력되도록 설정 (숫자만 입력해도 "-"가 붙도록)
비밀번호 검증 유틸 함수 구현 (검증 통과되지 않을시 api요청 금지)
공유과제
링크 첨부(팀 블로그 링크) :
https://citrine-tractor-afe.notion.site/LightHouse-5bf48dcfc60f427c9ec920111ea29ad5?pvs=4
📌 내가 새로 알게 된 부분
다음과 같이 같은 레이아웃을 적용할 때, 레이아웃을 재사용할 수 있도록
ModalLayout
을 만들고와 같이
Login
,Mypage
페이지에서 사용해 같은 UI 를 제공했습니다.스타일 코드를 재사용하기 위해
buttonStyle
변수로 만들고BtnContainer
와StyledLink
에서 재사용했습니다💎 구현과정에서의 고민과정(어려웠던 부분) 공유!
문제:
CommonBtn
이라는 공통 버튼 컴포넌트가 있을 때, link 가 주어졌을 때는 라우팅을 해야 하고 onClick 이 주어졌을 때는 onClick 함수를 실행해야 했습니다.처음에는 Link 하는 역할만을 염두에 두고
BtnContainer
안에 Link 만을 넣었더니 Onclick을 실행할 수 없어서,BtnContainer
에서 onClick을 수행하도록 하고하지만 어떤 역할을 하는지 알아보기 어렵다고 생각해 아래와 같이 구조를 변경했습니다.
해결: props 를 검사해 각 경우에 따라 다른 동작을 하도록
handleClick
를 만들고,link 인 경우 Link 컴포넌트를 이용하는 대신
useNavigate()
훅을 이용한 네비게이션을 하도록 한 후 버튼에 onClick을 넘겨주는 방식으로 수정했습니다api 에서 에러가 발생했을 때
를 구분해 1번의 경우 메시지를 alert 로 출력해야 해서,
1번을 구분하는 방식으로 아래와 같이
isAxiosError(error)
를 사용했는데,이 방법이 예외를 올바르게 처리할 수 있는 방법인지 궁금합니다.
🥺 소요 시간
10h
🌈 구현 결과물
https://citrine-tractor-afe.notion.site/week4-859a05e739d047de90648bf2a706fe6c?pvs=4