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

[임윤혁] Week13 #469

Conversation

oauch
Copy link
Collaborator

@oauch oauch commented Dec 3, 2023

요구사항

기본

  • /folder’ 페이지를 Next.js 프로젝트에 맞게 변경 및 이전 했나요?
  • ‘/shared’ 페이지를 Next.js 프로젝트에 맞게 변경 및 이전 했나요?
  • 다른 페이지로 이동이 필요한 곳에 next/link의 Link를 적용했나요?
  • Input 컴포넌트에 값이 없는 경우 회색의 placeholder값을 볼 수 있나요?
  • Input 컴포넌트에 focus in 하면 파랑색 테두리를 볼 수 있나요?
  • Input 컴포넌트에 눈 모양 아이콘을 누르면 비밀번호 가리기/보기 기능이 토글 되나요?
  • Input 컴포넌트에 값이 에러케이스일 경우 빨강색 테두리와 에러 메세지를 볼 수 있나요?
  • Input 컴포넌트에서 focus out 하면 실행할 함수를 설정할 수 있나요?

심화

X

주요 변경사항

  • React -> Next.js로 마이그레이션 했습니다.
  • input 컴포넌트 개발

스크린샷

Dec-03-2023 15-56-49

멘토에게

  • 카카오톡 공유하기에서 계속 공유 URL이 기본 페이지 'localhost:3000'으로만 공유가 되서 이 부분은 어떤식으로 구현 해야될지 모르겠습니다.
  • URL 공유하기는 잘 넘어오는데 카카오톡 공유하기는 시도 해봤지만 적용이 되질 않아서 질문 남깁니다

@oauch oauch requested a review from yusunghyun December 3, 2023 07:02
@oauch oauch self-assigned this Dec 3, 2023
@oauch oauch added the 순한맛🐑  마음이 많이 여립니다.. label Dec 3, 2023
Copy link
Collaborator Author

@oauch oauch left a comment

Choose a reason for hiding this comment

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

Input 컴포넌트에서 props에 관련된 것들은 src/constants/input.ts에서 사용하시면 됩니다

setModalIsOpen(!isModalOpen);
};

const showShareKakao = () => shareKakaoLink(window.location.href, asPath);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

카카오톡 부분은 이 부분 입니다 😶

Comment on lines +37 to +48
<ModalTitle title={title} />
{title === "폴더 추가" ||
title === "폴더에 추가" ||
title === "폴더 이름 변경" ? null : (
<ModalSubTitle subTitle={subTitle} />
)}
{title === "폴더 공유" ? (
<ModalShareButton
shareKakao={shareKakao}
shareLink={shareLink}
shareFacebook={shareFacebook}
/>
Copy link
Collaborator

@qooktree1 qooktree1 Dec 3, 2023

Choose a reason for hiding this comment

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

모달들을 하나의 폴더에서 관리하군요..! 참고하겠습니다 👍

Comment on lines +70 to +80
const ModalWrapper = styled.div`
width: 100%;
height: 100vh;
display: flex;
position: fixed;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.4);
bottom: 0px;
z-index: 2;
`;
Copy link
Collaborator

@qooktree1 qooktree1 Dec 3, 2023

Choose a reason for hiding this comment

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

이전 페어 프로그래밍 때 말씀하신 점 참고해서 styled-components를 component 코드에 합치는 것을 제 코드에도 적용하게 되었습니다! 감사합니다 👍👍

Comment on lines +8 to +15

const SOCIAL_LIST = [
{
id: 0,
href: "https://www.facebook.com/",
imgSrc: Facebook,
alt: "페이스북 아이콘",
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

constants 폴더에 상수 값들을 따로 모아두어 사용하셔도 좋을 것 같아요

Comment on lines +9 to +11
.input::placeholder {
color: #9fa6b2;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

::placeholder 라는 가상 요소를 사용해서 placeholder만 style을 줄 수 있군요! 몰랐던 부분인데 감사합니다 👍

Copy link
Collaborator

@summerkimm summerkimm left a comment

Choose a reason for hiding this comment

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

윤혁님 13주차 과제하느라 고생많으셨습니다!!

const [searchResult, setSearchResult] = useState("");
const { asPath } = useRouter();

const folderParams = useSearchParams(); // setFolderParams 이걸 뭘로 해야될까요... useSearchParams에 대한 공부가 아직 더 필요한..
Copy link
Collaborator

Choose a reason for hiding this comment

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

저도 .. 필요한 .. 🥲

function FolderAddButton({ onClick }: OnclickProps) {
return (
<button className={styles.folderAdd} onClick={onClick} value="addFolder">
폴더 추가 +
Copy link
Collaborator

Choose a reason for hiding this comment

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

어랏 전 이미지로 넣었는데.. 그냥 + 넣어도 됐었군요..!

const buttonName = e.currentTarget.value;
const url = e.currentTarget.id;

switch (buttonName) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 부분 배워갑니다 👀

Copy link
Collaborator

Choose a reason for hiding this comment

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

와... 모달들이 공통된 부분이 많아서 어떻게 처리하지 고민만 하고 있었는데... 대박입니다

Copy link
Collaborator

Choose a reason for hiding this comment

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

대.박. 💯💯💯


@media (max-width: 1199px) {
.introWrapper {
padding: 60px 32px 90px 32px;
Copy link
Collaborator

Choose a reason for hiding this comment

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

패딩 부분은 px로 남겨두셨는데.. 혹시 이유가 있나요 ? 👀

Copy link
Collaborator Author

@oauch oauch Dec 5, 2023

Choose a reason for hiding this comment

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

이게 예전에 했던거라서 그런거 같은데, 저도 원래는 모든걸 rem으로 했었는데
저번 파트 멘토링 때, rem으로 주는거는 font-size만 해주는게 좋다고 하시더라고요

이유는 만약 rem으로 레이아웃을 지정하게 되면 애상치 못하게 레이아웃이 깨지는 경우도 발생하기 때문에
레이아웃과 관련된 것들 = px / font-size = rem로 요즘은 하고 있습니다

Copy link
Collaborator

@yusunghyun yusunghyun left a comment

Choose a reason for hiding this comment

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

고생많으셨습니다 윤혁님!
TS에 이어 대대적인 migrate 작업이라 양이 많았을 것 같은데 잘 완료하셨네요!

첫 리뷰라서 제가 중요하게 생각하는 부분들을 설명드리자면

  1. 흐름을 중요시 생각합니다. css 각 스타일, 한줄한줄 코드 보다는 하나의 사용자 액션이 흘러와서 보기좋게 코드를 따라갈 수 있는지 거시적인 영역을 중요시 하고 작은 단위의 로직은 자유롭게 봅니다
  2. 따라서 리뷰의 내용이 가르킨 로직이 비슷한 코드들은 대부분 해당이 된다고 생각해주세요!
  3. 한단계 성장하기. 저도 누구도 모든 코드를 전부 리펙토링할 수는 없다고 생각하고 해당 코드레벨에서 한단계 올릴 수 있는 리뷰가 가장 좋다고 생각합니다.

크게는 위와 같이 리뷰를 진행했구요 대댓글, 코멘트도 환영인데 놓칠 가능성이 존재해서 태그 혹은 DM을 더 권장드립니다!

이번 위클리 미션은 전체적으로 옮기는게 주 내용이라서 새로 작성된 코드가 많이 없어서 �아래는 다음 작업때 반영하면 윤혁님에게 도움이 되실 내용입니다.

  1. 커스텀 훅
    아마 다음 작업이 next의 새 기능 일 것 같은데 그 사이에 여유가 있다면 윤혁님께 도움이 되는 작업은 커스텀훅이 1순위 일 것 같아요 예를들어 folder.tsx같은 경우 큰 파일부터 커스텀훅을 적용하며 분리하면 차후 작업에도 수월하실 것 같습니다. (추가로 useCallback도 같이 쓰면 좋을 것 같지만 후순위 같네용)

  2. type 분리
    d.ts에 타입을 모아두셨는데 조금은 생소하고 참조, import 되는게 모호해서 추후 유지관리를 위해서는 분리하는게 좋을 것 같아요. 일반적으로 d.ts에서는 global 타입이나 써드파티에 주로 쓰입니당
    일반적인 컴포넌트의 Props타입은 해당 컴포넌트 위에 두는 편 이구요 재사용이 되면 src/type.ts 에 넣거나 model등의 폴더를 만드는 편 입니다! 이 작업은 새 작업할 때 적용하거나 점진적으로 하기를 권장드립니다!

  3. getServerSideProps 사용하기
    next에 너무 많은 기능을 한번에 사용하기 어려우면 getServerSideProps를 적용하며 단순 fetch역할만 하는 useEffect들을 대신하는 작업을 하시면 도움이 되실 것 같습니다

너무 많이 수정하면 어려울 수도 있으니 위 내용과 코멘트 부분들 전체적으로 적용해주시면 좋을 것 같습니다~!

아차, 팀원분들 코드리뷰도 꼭 부탁드려요!

Comment on lines +1 to +17
interface Card {
url?: string;
title?: string;
description?: string;
}

export function filterCardsSearch(cards: Card[], searchResult: string): any {
const filteredCards = cards.filter((card) =>
[
card.url?.toLowerCase(),
card.title?.toLowerCase(),
card.description?.toLowerCase(),
].some((val) => val?.includes(searchResult.toLowerCase()))
);

return filteredCards;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

d.ts에 있는 interface Cardtitle을 추가하고 여기서 사용하고 return 타입을 Card[]로 수정한다면 더 좋을 것 같습니당

Comment on lines +43 to +55
const getAllFolder = async () => {
const response = await fetch(
`https://bootcamp-api.codeit.kr/api/users/1/links`,
{ method: "GET" }
);

if (!response.ok) {
throw new Error("전체 폴더 데이터 가져오기 에러 발생");
}

const body = await response.json();
return body.data;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

(정확한 타입은 안맞을 수도 있습니다)

const getAllFolder = async () => {
  const response = await fetch(
    `https://bootcamp-api.codeit.kr/api/users/1/links`,
    { method: "GET" }
  );

  if (!response.ok) {
    throw new Error("전체 폴더 데이터 가져오기 에러 발생");
  }

  const body = await response.json();
  return body.data as Card[];
};

일단은 해당 fetch가 folders.tsx/setCards에 쓰이고 cards의 타입은 Card여서 이렇게 작성해보았습니다.

위는 설명을 위한 예시이구요 위 방식 처럼 api들을 swagger, console, network탭 등을 통해서 서버 api에 알맞는 type을 만들어 놓는 것을 추천드립니다. (현재는 interface Card와 유사하네용)

filterCardsSearchapi.tsx의 파일들의 타입 처리가 끝나면 folders.tsx에서 cards state 선언 부분을 아래와 같이 수정할 수 있습니다.

const [cards, setCards] = useState<Card[]>([]);

filterCardsSearch로 받은 setCards의 타입을 보니 never[]로 되어있어서 한번 연결고리들을 체크해보았어요!
그러면 더욱 안전하고 연결적인 코드가 되어 좋을 것 같습니다 :)

Comment on lines +17 to +20
templateArgs: {
domain: window.location.origin,
path: url + pathName,
},
Copy link
Collaborator

Choose a reason for hiding this comment

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

kakao key가 없어서 테스트가 안됩니다 ㅠㅜㅠㅜ
window.location.origin, url, pathName 에 값이 무엇이 오는지 잘 확인해봐야겠지만
카카오 안되신다는 부분 이 domain 을 지우면 해결되실까 생각이 듭니다!

@oauch 계속 안되신다면 DM으로 api key 주시면 디버깅 해볼께요 :)

Comment on lines +31 to +32
const folderParams = useSearchParams(); // setFolderParams 이걸 뭘로 해야될까요... useSearchParams에 대한 공부가 아직 더 필요한..
const initFolderId: string | null = folderParams.get("folderId");
Copy link
Collaborator

Choose a reason for hiding this comment

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

NIT
page router에서도 app router 훅이 잘 작동하네요?!
저는 useRouter()query.folderId 이렇게 사용하곤 했습니다 아니면
하단에 아래처럼 코드를 작성하면 getServerSideProps로도 시작할 수 있을 것 같습니다

export const getServerSideProps: GetServerSideProps = async (context) => {
  console.log('context : ',context)

  return {
    props: {
        // 원하는 값들
    },
  };
};

Comment on lines +17 to +19
const visiblePassword = () => {
setIsVisible(!isVisible);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

const visiblePassword = () => {
    setIsVisible(prev=>!prev);
  };

setState할 때 직접 선언한 state에 참조를 걸지 않고도 콜백을 이용해서 최근 state값을 가지고 올 수 있습니다 전체적으로 수정 보다는 이 방식을 추천드립니다
그 이유는 함수를 useCallback으로 감싸보면 더 명확해지는데요 setState만 작동해도 되지만 state가 변화되면서 의존성이 트리거 되고 새로 선언이 됩니다.
더 쉽게는 useEffect에서 setState(state) 이렇게 작성을 하면 useEffect가 set한 행위로 본인이 다시 랜더링 되는 무한 루프에 걸리게 되지요

그래서 '저는' 최대한 한 블록스코프에 state, setState 를 동시에 놓지 않도록 하는 습관을 가지고 있습니당
따라서 전체적으로 setState 사용 부분들을 좀 더 순수하게 리펙토링하면 성능면으로도 좋아질 것 같아요!

Comment on lines +61 to +66
if (!searchResult) {
setCards(introResult);
} else {
const filterCard = filterCardsSearch(introResult, searchResult);
setCards(filterCard);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

NIT
개인적으로 else문을 지양하는 편이라 저라면 이 부분도 바로 위 로직처럼 얼리리턴패턴으로 작성할 것 같습니당


const getFolderLinks = async (folderId: string) => {
const response = await fetch(
`https://bootcamp-api.codeit.kr/api/users/1/links?folderId=${folderId}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

도메인 url이 자주 쓰여서 constants같은 곳에 모아서 재사용하면 좋을 것 같습니당

};

useEffect(() => {
folderInfo(initFolderId as string);
Copy link
Collaborator

Choose a reason for hiding this comment

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

매번 as를 넣기 보다는 null 방어를 하던가 아래처럼 무조건 string으로 운영되도록 하는게 관리하기에 더 좋을 것 같습니다 :)

const initFolderId: string = folderParams.get("folderId") || '';

Comment on lines +110 to +117
const showShareFacebook = () => {
window.open(`http://www.facebook.com/sharer.php?u=${window.location.href}`);
};

const showShareLinkCopy = async () => {
await navigator.clipboard.writeText(window.location.href);
alert("주소가 복사 되었습니다!");
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

이렇게 show prefix로 함수화 하는 패턴 좋네요 :) 해당 코드들은 react코드가 없어서 컴포넌트 혹은 페이지 밖에 두면 더 좋을 것 같아요! 저는 잘 모르겠으면 src/utils/ 에 몰아넣기도 합니다!

import { filterCardsSearch } from "src/utils/searchFilterCards";
import { shareKakaoLink } from "src/utils/shareKakaoLink";

function Folder() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 페이지가 선언 -> 함수 -> useEffect -> 컴포넌트 순으로 정렬되있어서 보이게 좋네요 :)

@yusunghyun yusunghyun merged commit 2aed961 into codeit-bootcamp-frontend:part3-임윤혁 Dec 4, 2023
@qooktree1
Copy link
Collaborator

혹시 Styled-components와 css-modules 둘 다 사용하시는 이유가 따로 있으신가요??

Comment on lines +1 to +10
declare module "*.scss" {
const content: { [className: string]: string };
export = content;
}

declare module "*.jpg";
declare module "*.png";
declare module "*.jpeg";
declare module "*.gif";
declare module "*.svg";
Copy link
Collaborator

@qooktree1 qooktree1 Dec 5, 2023

Choose a reason for hiding this comment

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

[궁금] 여기는 무슨 코드인가요??

Copy link
Collaborator

@zermzerm zermzerm left a comment

Choose a reason for hiding this comment

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

역시 듣던 대로 고수셨네요!
코드도 깔끔하시고 감탄만 하다가 끝나버렸습니다
수고 많으셨습니다!

const buttonName = e.currentTarget.value;
const url = e.currentTarget.id;

switch (buttonName) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

와... 모달들이 공통된 부분이 많아서 어떻게 처리하지 고민만 하고 있었는데... 대박입니다

@@ -0,0 +1,18 @@
// 타입
export const INPUT_TYPE = {
email: {
Copy link
Collaborator

Choose a reason for hiding this comment

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

준비가 철저하시네요! 대박

@@ -0,0 +1,88 @@
interface OnclickProps {
Copy link
Collaborator

Choose a reason for hiding this comment

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

덕분에 .d.ts파일로 타입 정리해두는 거 배웠습니다!

Comment on lines +41 to +51

let folderName;
if (currentId.length === 0) {
folderName = "전체";
setIsFunctionButtonShow(false);
} else {
folderName = currentId[0].name;
setIsFunctionButtonShow(true);
}

setFolderName(folderName);
Copy link
Collaborator

Choose a reason for hiding this comment

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

[궁금] 위의 23번째 줄에서 folderName을 state로 정의한 후에 folderInfo 함수 내부에서 folderName을 let으로 재정의했는데 오류가 나지 않는 부분이 궁금합니다. 위의 folderName은 전역으로 접근이 가능할 것 같아 질문 드립니다!

Comment on lines +114 to +119
@media (max-width: 1123px) {
width: 100%;
}
@media (max-width: 767px) {
width: 100%;
}
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 +13 to +15
const pathname = usePathname();
const sharedPage = pathname === "/shared";

Copy link
Collaborator

Choose a reason for hiding this comment

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

boolean 값으로 들어가는 변수값은 앞에 is를 붙여서 truthy 값인지 falsy 값인지 유추하게 만드는 것도 좋을 것 같아요!

isSharedPage

Comment on lines +6 to +7
<>
{folders.map((folder: any) => (
Copy link
Collaborator

Choose a reason for hiding this comment

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

위에서 folders 의 타입을 받은 것으로 보이는데 any는 되도록 지양하는게 좋을 것 같아요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
순한맛🐑  마음이 많이 여립니다..
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants