-
Notifications
You must be signed in to change notification settings - Fork 51
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
[이승연] week14 #534
The head ref may contain hidden characters: "part3-\uC774\uC2B9\uC5F0-week14"
[이승연] week14 #534
Conversation
…-week7 [이승연] week7
…-week8 [이승연] week8
…-week9 [이승연] week9
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 이하 구조는 템플릿이고 그 위에 구현들을 승연님이 하신거겟죠?
템플릿 코드를 이용해서 컴포넌트를 구조화하는것도 쉽지 않은작업이에요. 고생하셨습니다.
useEffect(() => { | ||
setIsNotSamePasswordValue(passwordValue !== checkPasswordValue); | ||
if (localStorage.getItem("accessToken")) router.push("/folder"); | ||
}, [passwordValue, checkPasswordValue]); | ||
|
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.
useEffect(() => {
setIsNotSamePasswordValue(passwordValue !== checkPasswordValue);
}, [passwordValue, checkPasswordValue]);
랑
useEffect(() => {
if (localStorage.getItem("accessToken")) router.push("/folder");
}, []);
로 나누면 좋을것같아요.
그리고
사실 isNotSamePasswordValue 는 useState로 관리 안해도 될거같네요. 이게 제가 누누히 말하던 파생상태입니다.
const isNotSamePasswordValue = passwordValue !== checkPasswordValue
const [checkPasswordValue, setCheckPasswordValue] = useState(""); | ||
const [isNotSamePasswordValue, setIsNotSamePasswordValue] = useState(false); | ||
const [emailValue, setEmailValue] = useState(""); | ||
let result; |
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.
아 뭔가 시도하려 했다가 지웠는데 그 잔해가 남아버렸습니다........ㅎㅎ..
|
||
return ( | ||
<SignUpLayout | ||
signTitle={<SignTitle pathName={pathName} />} |
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.
제가보기에는 SignTitle이 코드잇에서 제공하는 템플릿 ui 인거같은데, 그렇다면
const pathName = {
isSigninPage: currentPath === "/signin",
isSignupPage: currentPath === "/signup",
};
이렇게 계산하지 않아도,
signTitle={<SignTitle pathName={{isSigninPage:false,isSignupPage:true}}/>}
이렇게 표현할 수 있을것같아요.
이게 왜 더 좋다는 생각을 햇냐면, 컴포넌트만 봐도 딱 signup-page라는걸 알 수 있기 때문이에요. 기존처럼 pathName 으로 추상화 한 값을 이용했다면, currentPath가 뭔지 알아야 확신이 설거 아니에요?
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.
SignTitle은 제겁니다....🤭
<SignInLayout | ||
signTitle={<SignTitle pathName={pathName} />} | ||
emailInput={<InputUserInfo isPassword={false} />} | ||
passwordInput={<InputUserInfo isPassword={true} />} | ||
loginButton={<SignButton pathName={pathName} />} | ||
socialLogin={<SocialSign pathName={pathName} />} | ||
/> | ||
); |
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.
해당 코드에 있는 컴포넌들은 제가 만들긴했는데요ㅠㅠ
템플릿코드를 보니 형식이 레이아웃 컴포넌트를 만들고 프롭스로 컴포넌트를 내려주더라구요.
(이게 가능한것도 처음알았어요@.@)
그래서 오 쩌는뎅? 하고 따라서 레이아웃컴포넌트를 만들고 해당되는 컴포넌트들을 만들어서 프롭스로 내려줬습니다.
근데...너무 컴포넌트들을 쪼개서 UI상태로만 분류해놓은것이 문제가 된것 같습니다.
<SignInLayout
signTitle={<SignTitle pathName={pathName} />}
�signinForm={<SigninForm />} //요기 안에 인풋컴포넌트 및 로그인 버튼 묶어서 배치하기
socialLogin={<SocialSign pathName={pathName} />}
/>
구현하다보니 차라리 요런형태로 한번더 기능적으로 분리를 했어야 할것 같다는 생각이 들었습니다ㅠㅠ
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.
아 다 구현하신거에요? 참고만하고?
대단하네요!
더봐야겟네요
일단 form 기능을 묶어서 따로 넘겨주는식으로 구현하는게 더 좋은 접근방식 맞는거같아요! 좋은 고민을 하셨네요 ㅎㅎ
멘토님 혹시 InputUserInfo 컴포넌트 한번만 봐주실 수 있을까요? |
승연님 코드는 분량이 많아서 하루정도 더 보려고 해요 |
감사합니다! |
function handleChange(e: ChangeEvent<HTMLInputElement>) { | ||
isPassword ? setPassword(e.target.value) : setEmail(e.target.value); | ||
if (!e.target.value) { | ||
setIsWrongValue(true); | ||
} else { | ||
setIsWrongValue(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.
input이
passwordInput과 textInput (혹은 emailInput)두개로 나뉘어 있었다면 이런 분기처리 안해도 되겠죠?
생각하신대로 기능별로 컴포넌트 하나씩 나누는게 맞아요.
{isOverlapValue && pathName?.pathName.isSignupPage && ( | ||
<span className={cx("wrongValueMessage")}> | ||
{WRONG_VALUE_MESSAGE.overlapId} | ||
</span> | ||
)} |
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.
이런식으로 여러군데에서 사용하는 함수(혹은 컴포넌트)가 특정 지면이나기능에 종속적인것(pathName?.pathName.isSignupPage
이런 조건)을 지양해야해요.
컴포넌트는 말 그대로 재사용 가능한 조각이여서 어디에서도 쓸 수 있어야 해요. 특정 위치에서만 동작하는 분기가 있으면 좋지 못한 코드에요 ㅠㅠ
const SignTitle = ({ pathName }: PathName) => { | ||
const cx = classNames.bind(styles); | ||
return ( | ||
<div className={cx("title-box")}> | ||
<nav className={cx("logo")}> | ||
<Link href="/"> | ||
<Image src={LOGO_IMAGE} alt="Linkbrary 서비스 로고" fill /> | ||
</Link> | ||
</nav> | ||
<div className={cx("title-description-box")}> | ||
<p className={cx("title-description")}> | ||
{pathName.isSigninPage | ||
? "회원이 아니신가요? " | ||
: "이미 회원이신가요? "} | ||
<Link href={pathName.isSigninPage ? ROUTE.회원가입 : ROUTE.로그인}> | ||
<span className={cx("link")}> | ||
{pathName.isSigninPage ? "회원가입 하기" : "로그인 하기"} | ||
</span> | ||
</Link> | ||
</p> | ||
</div> | ||
</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.
SignTitle은 signup/ signin 둘줄 하나만 관리하는게 변하지 않는다면 차라리 isSignupPage 불리언값 하나만 관리해도 되었을것같네요.
그리고 분기를 다음과 같이 하면 좋을것같아요.
const CONSTANTS = {
signin:{
question:'회원이 아니신가요?',
buttonText: '회원가입 하기',
linkUrl: ROUTE.회원가입
},
signup:{
question:'이미 회원이신가요?',
buttonText: '로그인 하기',
linkUrl: ROUTE.로그인
}
}
이렇게 constant 하나 만든다음에,
const signinOrSignup = isSigninPage ? 'signin' : 'signup'
<p className={cx("title-description")}>
{CONSTANTS[signinOrSignup].question}
<Link href={CONSTANTS[signinOrSignup].linkUrl}>
<span className={cx("link")}>
{CONSTANTS[signinOrSignup].buttonText}
</span>
</Link>
</p>
useEffect(() => { | ||
execute(); | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [folderId]); |
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.
여기서 excute를 의존성 배열에 추가하지 않으면 제대로된 값을 excute 안할거같은데...
type FolderButtonProps = { | ||
text: string; | ||
onClick: MouseEventHandler<HTMLButtonElement>; | ||
isSelected?: boolean; |
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.
사용하는곳에서 isSelected가 undefined일 경우가 없던데, 왜 옵셔널하게 줬을까요? 옵셔널은 필요하지 않으면 안쓰는게 좋거든요
export const ROUTE = { | ||
랜딩: "/", | ||
로그인: "/signin", | ||
회원가입: "/signup", | ||
개인정보처리방침: "/privacy", | ||
FAQ: "/faq", | ||
}; | ||
|
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.
한글을 적절하게 사용하는 방식 저는 개인적으로 선호!
export const getElapsedTime = (createdAt: string) => { | ||
const now = new Date(); | ||
const createdAtDate = new Date(createdAt); | ||
const elapsedTime = now.getTime() - createdAtDate.getTime(); | ||
const { minute, hour, day, month, year } = TIME_IN_MILLISECONDS; | ||
|
||
if (year * 2 <= elapsedTime) { | ||
return `${Math.floor(elapsedTime / year)} years ago`; | ||
} | ||
if (year <= elapsedTime) { | ||
return `1 year ago`; | ||
} | ||
if (month * 2 <= elapsedTime) { | ||
return `${Math.floor(elapsedTime / month)} months ago`; | ||
} | ||
if (month <= elapsedTime) { | ||
return `1 month ago`; | ||
} | ||
if (day * 2 <= elapsedTime) { | ||
return `${Math.floor(elapsedTime / day)} days ago`; | ||
} | ||
if (day <= elapsedTime) { | ||
return `1 day ago`; | ||
} | ||
if (hour * 2 <= elapsedTime) { | ||
return `${Math.floor(elapsedTime / hour)} hours ago`; | ||
} | ||
if (hour <= elapsedTime) { | ||
return `1 hour ago`; | ||
} | ||
if (minute * 2 <= elapsedTime) { | ||
return `${Math.floor(elapsedTime / minute)} minutes ago`; | ||
} | ||
return `1 minute ago`; | ||
}; |
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 now = new Date();
에서 new Date()의 암묵적 의존성이 존재하기 때문이에요.
어떻게하면 테스트코드를 작성하면서 해당 기능을 유지할 수 있을까요?
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.
테스트 코드에 대한 개념이 아직 부족하여 사실 잘 모르겠습니다ㅠㅠ
해당 코드는 템플릿코드에서 구현한 내용인데,
제가 작성했던 예전 과제에서 테스트할때는 고정된 특정일의 Date객체를 생성하여 계산했었습니다.
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.
일단 답부터 드리자면 암묵적 의존성(new Date())를 명시적 의존성(함수의 인자로 받기)으로 바꾸는 방식으로 하면 테스트 가능한 함수가 됩니다.
import { useEffect } from "react"; | ||
import { ROOT_ID } from "./constant"; | ||
|
||
export const useBackgroundClick = (callback: (this: HTMLElement, ev: MouseEvent) => any) => { |
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.
callback의 리턴타입을 any보다는 void로 하는게좋을것같네요.
@@ -0,0 +1,3 @@ | |||
import { useEffect, useLayoutEffect } from "react"; | |||
|
|||
export const useEnhancedEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; |
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.
nit: useIsomorphicEffect 라고 많이들 표현해요.
요구사항
기본
[로그인 페이지] “회원 가입하기”를 클릭하면 ‘/signup’ 페이지로 이동하나요?
[로그인 페이지] 이메일 input에 placeholder는 “이메일을 입력해 주세요.”, 비밀번호 input에 placeholder는 “비밀번호를 입력해 주세요.”가 보이나요?
[로그인 페이지] 이메일 input에서 focus out 할 때, 값이 없을 경우 아래에 “이메일을 입력해주세요.” 에러 메세지가 보이나요?
[로그인 페이지] 이메일 input에서 focus out 할 때, 이메일 형식에 맞지 않는 값이 있는 경우 아래에 “올바른 이메일 주소가 아닙니다.” 에러 메세지가 보이나요?
[로그인 페이지] 비밀번호 input에서 focus out 할 때, 값이 없을 경우 아래에 “비밀번호를 입력해주세요.” 에러 메세지가 보이나요?
[로그인 페이지] 로그인 실패하는 경우, 이메일 input 아래에 “이메일을 확인해주세요.”, 비밀번호 input 아래에 “비밀번호를 확인해주세요.” 에러 메세지가 보이나요?
[로그인 페이지] 로그인 버튼 클릭 또는 Enter키 입력으로 로그인 실행 되나요?
[로그인 페이지] https://bootcamp-api.codeit.kr/docs 에 명세된 “/api/sign-in”으로 { “email”: “[email protected]”, “password”: “sprint101” } POST 요청해서 성공 응답을 받으면 “/folder”로 이동하나요?
[회원가입 페이지] “회원 가입하기”를 클릭하면 ‘/signin’ 페이지로 이동하나요?
[회원가입 페이지] 이메일 input에 placeholder는 “이메일을 입력해 주세요.”, 비밀번호 input에 placeholder는 “영문, 숫자를 조합해 8자 이상 입력해 주세요. ”비밀번호 확인 input에 placeholder는 “비밀번호와 일치하는 값을 입력해 주세요.”가 보이나요?
[회원가입 페이지] 이메일 input에서 focus out 할 때, 값이 없을 경우 “이메일을 입력해주세요.” 에러 메세지가 보이나요?
[회원가입 페이지] 이메일 input에서 focus out 할 때, 이메일 형식에 맞지 않는 값이 있는 경우 “올바른 이메일 주소가 아닙니다.” 에러 메세지가 보이나요?
[회원가입 페이지] 비밀번호 input에서 focus out 할 때, 값이 8자 미만으로 있거나 문자열만 있거나 숫자만 있는 경우, “비밀번호는 영문, 숫자 조합 8자 이상 입력해 주세요.” 에러 메세지가 보이나요?
[회원가입 페이지] 비밀번호 input과 비밀번호 확인 input의 값이 다른 경우, 비밀번호 확인 input 아래에 “비밀번호가 일치하지 않아요.” 에러 메세지가 보이나요?
[회원가입 페이지] 회원가입을 실행할 경우, 문제가 있는 경우 문제가 있는 input에 에러 메세지가 보이나요?
[회원가입 페이지] 회원가입 버튼 클릭 또는 Enter키 입력으로 회원가입 실행 되나요?
[회원가입 페이지] 이메일 중복 확인은 “/api/check-email” POST 요청해서 확인 할 수 있나요?
[회원가입 페이지] 유효한 회원가입 형식의 경우 “/api/sign-up” POST 요청하고 성공 응답을 받으면 “/folder”로 이동하나요?
[로그인, 회원가입 페이지 공통] 눈 모양 아이콘 클릭시 비밀번호의 문자열이 보이기도 하고, 가려지나요?
[로그인, 회원가입 페이지 공통] 비밀번호의 문자열이 가려질 때는 눈 모양 아이콘에는 사선이 그어져있고, 비밀번호의 문자열이 보일 때는 사선이 없는 눈 모양 아이콘이 보이나요?
[로그인, 회원가입 페이지 공통] 소셜 로그인에 구글 아이콘 클릭시 ‘https://www.google.com’, 카카오 아이콘 클릭시 ‘https://www.kakaocorp.com/page’로 이동하나요?
[로그인, 회원가입 페이지 공통] 로그인/회원가입시 성공 응답으로 받은 accessToken을 로컬 스토리지에 저장하나요?
[로그인, 회원가입 페이지 공통] 로그인/회원가입 페이지에 접근시 로컬 스토리지에 accessToken이 있는 경우 ‘/folder’ 페이지로 이동하나요?
[심화] 로그인, 회원가입 기능에 react-hook-form을 활용했나요?
주요 변경사항
스크린샷
멘토에게
(근데 main 브랜치를 base로 해야 올라가더라구요... 머지 말고 그냥 클로즈 해주시면 감사하겠습니다 ㅠㅠ)