Skip to content
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] 김하늘 #510

Conversation

han-kimm
Copy link
Collaborator

@han-kimm han-kimm commented Dec 10, 2023

요구사항

기본

  • “회원 가입하기”를 클릭하면 ‘/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을 활용했나요?

주요 변경사항

  • Week13 과 UI, 작동은 차이가 없고, 코드 리팩토링 위주입니다.

멘토에게

  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@han-kimm han-kimm requested a review from InSeong-So December 10, 2023 02:51
Copy link
Collaborator Author

@han-kimm han-kimm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리팩토링 주간이었습니다.
컴포넌트도 함수이므로 함수의 책임은 무엇인가에 대해서 생각해보면서,
관련된 코드를 묶는 과정(ex. useAllBlur, usePwVisibility ...) 이 재밌었습니다.

그럼에도 관심사의 분리나 디자인 패턴 등에 대해서 잘 모르고 있다는 점이 확실해서, 더 연습이 필요하네요! 부족함을 깨달아서 보람있는 위클리였습니다.

Comment on lines +17 to +36
const setRefForObserver: SetRefForObserver = useCallback((el) => {
if (!el) return;
if (el.tagName === "FOOTER") {
DOM.current.footer = el;
return;
}
if (el.tagName === "FORM") {
DOM.current.headerForm = el;
return;
}
if (el.tagName === "DIV") {
DOM.current.floatDiv = el;
return;
}
if (el.tagName === "INPUT" && el.parentElement?.tagName === "FORM") {
DOM.current.headerInput = el;
return;
}
DOM.current.floatInput = el;
}, []);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

week13 때 ref콜백함수를 (el) => dom.current.(속성명) = el 형식으로 작성했었는데, 왜 ref콜백으로 담는지에 대한 의도가 나타나지 않아서, 기명함수를 이용해서 의도를 전달하려고 했습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref설정함수를 선언적으로 호출할 수 있고,
옵저버 관련 로직이 훅 안에 정리되어서 명확해진 점이 좋았습니다.

@@ -8,29 +8,55 @@ export type Dom = {
footer: HTMLElement | null;
};

let isForm = false;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 리뷰를 토대로 전역변수 선언을 없애고 state로 바꾸었습니다.
state일 필요가 없는 값들은 ref로 바꾸었습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input 컴포넌트에서 눈 아이콘을 눌러 비밀번호를 보이게 하는 로직을 훅으로 모아서 정리했습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

훅으로 모은 이유는
(1) Wee13 Input 컴포넌트는 값 검증, 비밀번호 보이기 로직이 섞여서 읽기 힘든 상태이다.
(2) 이메일에 쓰이는 컴포넌트에서는 비밀번호 보이기 로직이 필요하지 않으므로,
이메일과 비밀번호를 다른 컴포넌트로 분리하고, 필요한 로직만 사용하도록 만든다.

제가 재사용성 때문에 Prop으로 여러 상황에 쓰이는 컴포넌트를 만드는 경향이 있는데,
코드의 가독성과 컴포넌트의 책임을 명확히 하기 위해서
로직이 다르면 컴포넌트를 분리하는 것이 중요하다는 걸 느꼈습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useVisibility.ts와 같은 이유로 useSignInput.ts로 값 검증 로직을 묶어두었습니다.

}
export default function FolderPage() {
const { setRefForObserver } = useObserver();
const id = useGetUserId();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/folder?a={accessToken} 방식으로 하니까 코드가 읽기 불편해지고, 임기응변 방식이라고 생각되어서,
일단 /folder는 CSR방식으로 id를 가져와서 렌더링하게 했습니다.

week15을 하면서 React-query를 사용해보겠습니다.

},
};
},
[]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/signin에서 어떤 Input 컴포넌트에 blur이벤트가 일어난 이후에,
submit 버튼을 누르면, 직전에 blur이벤트가 일어난 컴포넌트만 blur되는 오류가 있었습니다.

해결책은 useImperativeHandle 디펜던시 설정이었습니다.
Label 컴포넌트가 리렌더링이 일어날 경우 언마운트가 되어 ref가 null이 되고,
리렌더링이 일어난 컴포넌트의 useImperativeHandle에서 해당 핸들러함수만 ref.current에 담기는 현상이었습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRef는 알수록 재밌네요

import SignForm from "@/components/Main/sign/SignForm";
import SignLogo from "@/components/Main/sign/SignLogo";
import { StyledSection } from "@/components/Main/sign/SignSection.styled";
import SignSns from "@/components/Main/sign/SignSns";

export default function SignSection({ signin = false }: Signin) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

페이지를 구분하는 Prop은 없애고, 페이지 구분이 필요한 컴포넌트에서 router로 url 확인하는 과정을 추가했습니다.

@@ -8,7 +9,7 @@ interface Props {
export default function Profile({ profileImg, email }: Props) {
return (
<Container>
<ProfileImg src={profileImg} alt="프로필 사진" />
<ProfileImg src={profileImg ?? defaultProfileImg.src} alt="프로필 사진" />
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React 프로젝트처럼 이미지를 import해서 해당 변수를 src에 넣어주었더니 화면에 나타나지 않아서,
콘솔로 찍어보니 객체로 import 되는 사실을 알았습니다.
신기하네요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next에서는 이미지를 서빙하는 방식이 달라서 그렇답니다!

import { useRef } from "react";

export default function useInputAllBlur() {
const inputRef = useRef<InputRef>(null);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Input 컴포넌트에 직접 연결되지는 않고,
forwardRef로 Sign--Label 컴포넌트에서 메서드만 담아오는 Ref입니다.

Comment on lines +10 to +14
for (const f of Object.values(inputRef.current)) {
if (f instanceof Function) {
res = f();
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/signin/signup의 Input 컴포넌트 개수(2개, 3개)가 달라서
for of문을 통해서 내부 메서드(handleBlur)를 순회했습니다.

Copy link
Collaborator

@InSeong-So InSeong-So left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useImperativeHandle로 ref 핸들링을 유도한 게 신선했습니다!
대부분의 코멘트에서 의도를 잘 녹여주어서 이해하기 수월했답니다.
수고 많으셨어요🤗

Comment on lines +36 to +37
<Float ref={setRefForObserver}>
<Input ref={setRefForObserver} value={value} onChange={handleChange} placeholder="링크를 추가해보세요." />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 input은 어디에 사용되는건가요? 화면에도 출력되지 않아보이네요.

Comment on lines +6 to 10
const locate = useRef("/signin");
const accessToken = typeof window !== "undefined" ? sessionStorage.getItem("accessToken") : null;
if (accessToken) {
locate = `/folder?a=${accessToken}`;
locate.current = `/folder`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useRef를 이용하여 locate 값을 설정하고 있지만, useEffect나 조건부 렌더링을 활용하여 해당 값의 설정을 컴포넌트 마운트 이후에 하는 것이 좋을 수 있습니다.
컴포넌트 렌더링 사이클을 분리할 수 있어 코드가 더 명확해지거든요.

아래 return 구문의 <>는 제거해도 괜찮겠네요~

Comment on lines +13 to +22
<CardImg src={imageSource || image_source || "/nofileimg.png"} alt="카드 이미지" />
</WrapperCardImg>
<CardText>
<WrapperTime>
<TimeFlow createdAt={createdAt ?? (created_at as string)} />
{folder && <Kebab folder={folder} url={url} />}
</WrapperTime>
<H3>{title?.length > 40 ? title.slice(0, 40) + "..." : title}</H3>
<p>{description?.length > 50 ? description.slice(0, 50) + "..." : description}</p>
<p>{formatDate(createdAt ?? (created_at as string))}</p>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내부에 연산 값이 많아 가독성을 헤칩니다. 상단 변수로 올려서 computed하게 변수를 만들어 사용하는 것을 추천드려요.

Comment on lines +4 to +8
export const scrollDown = keyframes`
50% {
bottom: 11rem;
}
`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

애니메이션 알차네요👍

Comment on lines +3 to 5
interface Error {
$error: boolean;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error는 예약어이므로 다른 키워드를 권장합니다.

Comment on lines +7 to +8
const router = useRouter();
const signin = router.asPath === "/signin";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비슷한 코드가 여러 페이지에서 사용되므로 커스텀 훅으로 분리해보면 어떨까요?

@@ -8,7 +9,7 @@ interface Props {
export default function Profile({ profileImg, email }: Props) {
return (
<Container>
<ProfileImg src={profileImg} alt="프로필 사진" />
<ProfileImg src={profileImg ?? defaultProfileImg.src} alt="프로필 사진" />
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next에서는 이미지를 서빙하는 방식이 달라서 그렇답니다!

@@ -13,7 +13,7 @@ export const useGetUserId = () => {
const res = await axios.get("/api/users", { headers: { Authorization: accessToken } });
const { id } = res.data.data[0];
setId(id);
return;
return id;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id 반환의 의미가 있나요?

@InSeong-So InSeong-So merged commit dcfe4ed into codeit-bootcamp-frontend:part3-김하늘 Dec 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants