-
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
[임윤혁] Week13 #469
The head ref may contain hidden characters: "part3-\uC784\uC724\uD601-week13"
[임윤혁] Week13 #469
Conversation
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 컴포넌트에서 props에 관련된 것들은 src/constants/input.ts
에서 사용하시면 됩니다
setModalIsOpen(!isModalOpen); | ||
}; | ||
|
||
const showShareKakao = () => shareKakaoLink(window.location.href, asPath); |
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.
카카오톡 부분은 이 부분 입니다 😶
<ModalTitle title={title} /> | ||
{title === "폴더 추가" || | ||
title === "폴더에 추가" || | ||
title === "폴더 이름 변경" ? null : ( | ||
<ModalSubTitle subTitle={subTitle} /> | ||
)} | ||
{title === "폴더 공유" ? ( | ||
<ModalShareButton | ||
shareKakao={shareKakao} | ||
shareLink={shareLink} | ||
shareFacebook={shareFacebook} | ||
/> |
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 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; | ||
`; |
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.
이전 페어 프로그래밍 때 말씀하신 점 참고해서 styled-components를 component 코드에 합치는 것을 제 코드에도 적용하게 되었습니다! 감사합니다 👍👍
|
||
const SOCIAL_LIST = [ | ||
{ | ||
id: 0, | ||
href: "https://www.facebook.com/", | ||
imgSrc: Facebook, | ||
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.
constants 폴더에 상수 값들을 따로 모아두어 사용하셔도 좋을 것 같아요
.input::placeholder { | ||
color: #9fa6b2; | ||
} |
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.
::placeholder 라는 가상 요소를 사용해서 placeholder만 style을 줄 수 있군요! 몰랐던 부분인데 감사합니다 👍
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.
윤혁님 13주차 과제하느라 고생많으셨습니다!!
const [searchResult, setSearchResult] = useState(""); | ||
const { asPath } = useRouter(); | ||
|
||
const folderParams = useSearchParams(); // setFolderParams 이걸 뭘로 해야될까요... useSearchParams에 대한 공부가 아직 더 필요한.. |
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.
저도 .. 필요한 .. 🥲
function FolderAddButton({ onClick }: OnclickProps) { | ||
return ( | ||
<button className={styles.folderAdd} onClick={onClick} value="addFolder"> | ||
폴더 추가 + |
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 buttonName = e.currentTarget.value; | ||
const url = e.currentTarget.id; | ||
|
||
switch (buttonName) { |
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.
와... 모달들이 공통된 부분이 많아서 어떻게 처리하지 고민만 하고 있었는데... 대박입니다
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.
대.박. 💯💯💯
|
||
@media (max-width: 1199px) { | ||
.introWrapper { | ||
padding: 60px 32px 90px 32px; |
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.
패딩 부분은 px로 남겨두셨는데.. 혹시 이유가 있나요 ? 👀
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.
이게 예전에 했던거라서 그런거 같은데, 저도 원래는 모든걸 rem으로 했었는데
저번 파트 멘토링 때, rem으로 주는거는 font-size만 해주는게 좋다고 하시더라고요
이유는 만약 rem으로 레이아웃을 지정하게 되면 애상치 못하게 레이아웃이 깨지는 경우도 발생하기 때문에
레이아웃과 관련된 것들 = px
/ font-size = rem
로 요즘은 하고 있습니다
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.
고생많으셨습니다 윤혁님!
TS에 이어 대대적인 migrate 작업이라 양이 많았을 것 같은데 잘 완료하셨네요!
첫 리뷰라서 제가 중요하게 생각하는 부분들을 설명드리자면
- 흐름을 중요시 생각합니다. css 각 스타일, 한줄한줄 코드 보다는 하나의 사용자 액션이 흘러와서 보기좋게 코드를 따라갈 수 있는지 거시적인 영역을 중요시 하고 작은 단위의 로직은 자유롭게 봅니다
- 따라서 리뷰의 내용이 가르킨 로직이 비슷한 코드들은 대부분 해당이 된다고 생각해주세요!
- 한단계 성장하기. 저도 누구도 모든 코드를 전부 리펙토링할 수는 없다고 생각하고 해당 코드레벨에서 한단계 올릴 수 있는 리뷰가 가장 좋다고 생각합니다.
크게는 위와 같이 리뷰를 진행했구요 대댓글, 코멘트도 환영인데 놓칠 가능성이 존재해서 태그 혹은 DM을 더 권장드립니다!
이번 위클리 미션은 전체적으로 옮기는게 주 내용이라서 새로 작성된 코드가 많이 없어서 �아래는 다음 작업때 반영하면 윤혁님에게 도움이 되실 내용입니다.
-
커스텀 훅
아마 다음 작업이 next의 새 기능 일 것 같은데 그 사이에 여유가 있다면 윤혁님께 도움이 되는 작업은 커스텀훅이 1순위 일 것 같아요 예를들어 folder.tsx같은 경우 큰 파일부터 커스텀훅을 적용하며 분리하면 차후 작업에도 수월하실 것 같습니다. (추가로 useCallback도 같이 쓰면 좋을 것 같지만 후순위 같네용) -
type 분리
d.ts에 타입을 모아두셨는데 조금은 생소하고 참조, import 되는게 모호해서 추후 유지관리를 위해서는 분리하는게 좋을 것 같아요. 일반적으로 d.ts에서는 global 타입이나 써드파티에 주로 쓰입니당
일반적인 컴포넌트의 Props타입은 해당 컴포넌트 위에 두는 편 이구요 재사용이 되면 src/type.ts 에 넣거나 model등의 폴더를 만드는 편 입니다! 이 작업은 새 작업할 때 적용하거나 점진적으로 하기를 권장드립니다! -
getServerSideProps 사용하기
next에 너무 많은 기능을 한번에 사용하기 어려우면 getServerSideProps를 적용하며 단순 fetch역할만 하는 useEffect들을 대신하는 작업을 하시면 도움이 되실 것 같습니다
너무 많이 수정하면 어려울 수도 있으니 위 내용과 코멘트 부분들 전체적으로 적용해주시면 좋을 것 같습니다~!
아차, 팀원분들 코드리뷰도 꼭 부탁드려요!
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; | ||
} |
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.
d.ts
에 있는 interface Card
에 title
을 추가하고 여기서 사용하고 return
타입을 Card[]
로 수정한다면 더 좋을 것 같습니당
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; | ||
}; |
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 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
와 유사하네용)
filterCardsSearch
와 api.tsx
의 파일들의 타입 처리가 끝나면 folders.tsx
에서 cards state
선언 부분을 아래와 같이 수정할 수 있습니다.
const [cards, setCards] = useState<Card[]>([]);
filterCardsSearch
로 받은 setCards의 타입을 보니 never[]로 되어있어서 한번 연결고리들을 체크해보았어요!
그러면 더욱 안전하고 연결적인 코드가 되어 좋을 것 같습니다 :)
templateArgs: { | ||
domain: window.location.origin, | ||
path: url + 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.
kakao key
가 없어서 테스트가 안됩니다 ㅠㅜㅠㅜ
window.location.origin
, url
, pathName
에 값이 무엇이 오는지 잘 확인해봐야겠지만
카카오 안되신다는 부분 이 domain
을 지우면 해결되실까 생각이 듭니다!
@oauch 계속 안되신다면 DM으로 api key 주시면 디버깅 해볼께요 :)
const folderParams = useSearchParams(); // setFolderParams 이걸 뭘로 해야될까요... useSearchParams에 대한 공부가 아직 더 필요한.. | ||
const initFolderId: string | null = folderParams.get("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.
NIT
page router에서도 app router 훅이 잘 작동하네요?!
저는 useRouter()
의 query.folderId
이렇게 사용하곤 했습니다 아니면
하단에 아래처럼 코드를 작성하면 getServerSideProps로도 시작할 수 있을 것 같습니다
export const getServerSideProps: GetServerSideProps = async (context) => {
console.log('context : ',context)
return {
props: {
// 원하는 값들
},
};
};
const visiblePassword = () => { | ||
setIsVisible(!isVisible); | ||
}; |
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 visiblePassword = () => {
setIsVisible(prev=>!prev);
};
setState할 때 직접 선언한 state에 참조를 걸지 않고도 콜백을 이용해서 최근 state값을 가지고 올 수 있습니다 전체적으로 수정 보다는 이 방식을 추천드립니다
그 이유는 함수를 useCallback으로 감싸보면 더 명확해지는데요 setState만 작동해도 되지만 state가 변화되면서 의존성이 트리거 되고 새로 선언이 됩니다.
더 쉽게는 useEffect에서 setState(state) 이렇게 작성을 하면 useEffect가 set한 행위로 본인이 다시 랜더링 되는 무한 루프에 걸리게 되지요
그래서 '저는' 최대한 한 블록스코프에 state, setState 를 동시에 놓지 않도록 하는 습관을 가지고 있습니당
따라서 전체적으로 setState 사용 부분들을 좀 더 순수하게 리펙토링하면 성능면으로도 좋아질 것 같아요!
if (!searchResult) { | ||
setCards(introResult); | ||
} else { | ||
const filterCard = filterCardsSearch(introResult, searchResult); | ||
setCards(filterCard); | ||
} |
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
개인적으로 else문을 지양하는 편이라 저라면 이 부분도 바로 위 로직처럼 얼리리턴패턴으로 작성할 것 같습니당
|
||
const getFolderLinks = async (folderId: string) => { | ||
const response = await fetch( | ||
`https://bootcamp-api.codeit.kr/api/users/1/links?folderId=${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.
도메인 url이 자주 쓰여서 constants같은 곳에 모아서 재사용하면 좋을 것 같습니당
}; | ||
|
||
useEffect(() => { | ||
folderInfo(initFolderId as 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.
매번 as를 넣기 보다는 null 방어를 하던가 아래처럼 무조건 string으로 운영되도록 하는게 관리하기에 더 좋을 것 같습니다 :)
const initFolderId: string = folderParams.get("folderId") || '';
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("주소가 복사 되었습니다!"); | ||
}; |
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.
이렇게 show prefix로 함수화 하는 패턴 좋네요 :) 해당 코드들은 react코드가 없어서 컴포넌트 혹은 페이지 밖에 두면 더 좋을 것 같아요! 저는 잘 모르겠으면 src/utils/ 에 몰아넣기도 합니다!
import { filterCardsSearch } from "src/utils/searchFilterCards"; | ||
import { shareKakaoLink } from "src/utils/shareKakaoLink"; | ||
|
||
function Folder() { |
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 -> 컴포넌트 순으로 정렬되있어서 보이게 좋네요 :)
혹시 Styled-components와 css-modules 둘 다 사용하시는 이유가 따로 있으신가요?? |
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"; |
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 buttonName = e.currentTarget.value; | ||
const url = e.currentTarget.id; | ||
|
||
switch (buttonName) { |
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.
와... 모달들이 공통된 부분이 많아서 어떻게 처리하지 고민만 하고 있었는데... 대박입니다
@@ -0,0 +1,18 @@ | |||
// 타입 | |||
export const INPUT_TYPE = { | |||
email: { |
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.
준비가 철저하시네요! 대박
@@ -0,0 +1,88 @@ | |||
interface OnclickProps { |
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.
덕분에 .d.ts파일로 타입 정리해두는 거 배웠습니다!
|
||
let folderName; | ||
if (currentId.length === 0) { | ||
folderName = "전체"; | ||
setIsFunctionButtonShow(false); | ||
} else { | ||
folderName = currentId[0].name; | ||
setIsFunctionButtonShow(true); | ||
} | ||
|
||
setFolderName(folderName); |
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.
[궁금] 위의 23번째 줄에서 folderName을 state로 정의한 후에 folderInfo 함수 내부에서 folderName을 let으로 재정의했는데 오류가 나지 않는 부분이 궁금합니다. 위의 folderName은 전역으로 접근이 가능할 것 같아 질문 드립니다!
@media (max-width: 1123px) { | ||
width: 100%; | ||
} | ||
@media (max-width: 767px) { | ||
width: 100%; | ||
} |
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 pathname = usePathname(); | ||
const sharedPage = pathname === "/shared"; | ||
|
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.
boolean 값으로 들어가는 변수값은 앞에 is를 붙여서 truthy 값인지 falsy 값인지 유추하게 만드는 것도 좋을 것 같아요!
isSharedPage
<> | ||
{folders.map((folder: 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.
위에서 folders 의 타입을 받은 것으로 보이는데 any는 되도록 지양하는게 좋을 것 같아요!
요구사항
기본
심화
X
주요 변경사항
스크린샷
멘토에게