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

[배진한] Sprint8 #116

Conversation

Jin-coding-333
Copy link
Collaborator

@Jin-coding-333 Jin-coding-333 commented Dec 8, 2024

📋 스프린트 미션 요구사항

배포 사이트(배진한_Sprint8)

기본 요구사항

공통

  • Github에 스프린트 미션 PR을 생성합니다.
  • Next.js를 사용해 프로젝트를 진행합니다.

자유 게시판 페이지

  • 게시글 목록에서 드롭다운을 사용하여 **"최신 순"**으로 정렬할 수 있도록 구현합니다.
  • 게시글 목록 데이터를 본인이 만든 GET 메서드 API를 활용하여 가져옵니다.
  • 게시글 제목에 검색어가 포함되면 해당 게시글을 검색할 수 있도록 구현합니다.
  • 이미지는 디폴트 이미지를 사용하여 프론트엔드에서 처리합니다.
  • 게시글 닉네임 및 좋아요 개수는 임의값으로 프론트엔드에서 처리합니다.
  • 베스트 게시글은 최신순 3개 게시글을 요청하여 데이터를 가져오고 구현합니다.
  • 특정 게시글을 클릭하면 해당 게시물의 상세 페이지로 이동합니다.

게시글 등록 & 수정 페이지

  • 각 input 필드에 적절한 placeholder 값을 추가합니다.
  • 모든 input 필드에 값이 입력되면 '등록' 버튼이 활성화됩니다.
  • 게시글 등록 시, POST 메서드 API를 활용하여 데이터를 저장합니다.
  • '등록' 버튼 클릭 시, 해당 게시물 상세 페이지로 이동합니다.
  • 게시글 수정 페이지는 등록 페이지와 동일한 UI로 구성합니다.
  • 게시글 수정 시, PATCH 메서드 API를 활용하여 데이터를 업데이트합니다.

게시글 상세 페이지

  • 게시글 상세 데이터를 GET 메서드 API로 가져옵니다.
  • 게시글 삭제 시, DELETE 메서드 API를 사용합니다.
  • 댓글 입력 시, 값이 입력되면 '등록' 버튼이 활성화됩니다.
  • 댓글 등록 시, POST 메서드 API를 사용하여 저장합니다.
  • 댓글 수정 시, PATCH 메서드 API를 활용하여 데이터를 업데이트합니다.
  • 댓글 삭제 시, DELETE 메서드 API를 사용합니다.

심화 요구사항

공통

  • 디자인 시안에 따라 반응형 디자인을 구현합니다.
  • (선택 사항) 기존 React 코드를 Next.js로 마이그레이션합니다.
    • 주의: 마이그레이션에는 시간이 소요될 수 있으므로 선택적으로 진행합니다.

멘토에게

  • 셀프 코드리뷰 이어나가겠습니다.

스크린샷

404페이지
screencapture-localhost-3000-Items-2024-12-08-03_50_33

게시글 목록 페이지
screencapture-localhost-3000-CommunityFeed-2024-12-09-03_23_11

게시글 상세 페이지(댓글0)
screencapture-localhost-3000-ArticleDetail-0cfe5986-2435-42fa-8eeb-622173523b86-2024-12-09-03_23_33

게시글 상세 페이지(댓글X)
screencapture-localhost-3000-ArticleDetail-de93d517-0be3-4d82-9991-2976d0d3389a-2024-12-09-03_56_53

홈 페이지
screencapture-localhost-3000-2024-12-08-03_51_21
486px_이하
750px_이하

브랜치를 잘못 생성한 상태로 진행해서 다시 브랜치를 생성함
아래는 이전 커밋의 내용만을 가져온 것임

feat: Nav 기능 구현 완료
styles: Nav.module.css 스타일 작성 완료
style: global.css 작성
chore: Home CSS 파일 경로 수정
chore: 기본적인 파일 구조 세팅
chore: sprint8 디렉토리 생성 & next.js 설치 & 필요 없는 파일 정리
@Jin-coding-333 Jin-coding-333 self-assigned this Dec 8, 2024
@Jin-coding-333 Jin-coding-333 added 미완성🫠 완성은 아니지만 제출합니다... 순한맛🐑 마음이 많이 여립니다.. 진행 중 🏃 스프린트 미션 진행중입니다. labels Dec 8, 2024
Copy link
Collaborator

@kimjong95 kimjong95 left a comment

Choose a reason for hiding this comment

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

대체적으로 잘 작성해주신것 같습니다. 프론트엔드에서의 최적화는 항상 고민해봐야할 과제이며 컴포넌트를 어떻게 분리할지, 어떻게 관리할지에 대한 고민도 항상 따라오는 문제입니다.

next라는 새로운 스펙을 학습하시면서 고민해봐야 할 것은 next가 우리에게 어떤 이로움을 제공하고, 이러한 프레임워크를 사용하려면 어떤것들을 지켜야하는지에 대한 고민이 필요합니다.
때문에 라이브러리(react)를 사용하는것은 크게 제약이 없지만 프레임워크(next)를 사용하는것에는 제약이 있는만큼 공식문서를 (pageRouter 기준으로)다시한번 숙지하시고 사용해주시면 좋을 것 같습니다.

import styles from "@/styles/pages/404.module.css";
import Link from "next/link";

function NotFound() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

404페이지를 따로 구성해주신거 좋습니다. 유효하지 않은 router를 제어하기위한 방식으로 사용되고 있고, 실제로도 이렇게 사용하고 있습니다.
다만 인지하고 계실수 있지만 next pageRouter기준으로 404페이지는 SSG형태로 구성됩니다. 즉 Static페에지로 빌드타임에 결정되고, 그내용을 유저에게 노출하는것 입니다. 원래 SSG로 제공하려면 getStaticProps함수를 사용해야하지만 404는 예외적으로 강제 static으로 사용된다는 점 입니다.
추후에 404페이지 이지만 동적으로 컨트롤 해야한다(유저도메인에 대한 국제화, 테마 같은 값으로 제어)면 대안이 필요할 수 있습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

404페이지는 강제로 static이 되는지 몰랐네요. 인지하겠습니다.

import Comments from "@/components/ArticleDetail/Comments";
import NoneComments from "@/components/ArticleDetail/NoneComments";

function ArticleDetail() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

페이지라우터에 조건에맞게 라우팅을 구성해주신 것 좋습니다.
다만 우리가 웹서비스에서 url을 제공할때는 통용되는 규칙이 존재합니다.
아래 링크의 내용중 위반된 내용을 정리하자면 많은 규칙중 url Path는 소문자로 작성해야된다는 점이 있습니다.

추가자료

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

page 디렉토리 안의 파일 명을 모두 소문자와 하이픈(-)으로 수정 했습니다.


articleLoadHandler();
commentLoadHandler();
}, [articleId, commentPost]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

컴포넌트를 구성해주시면서, 서버로의 API요청과 UI(JSX)를 구분해주신것 아주 좋습니다. 더 나아가서 이러한 내용들을 customHooks를 이용해서 파일자체를 분리시켜 관리하는법도 존재합니다.
분리된 파일에서 business, ui요소를 각각처리(관심사분리)하고, UI에서는 명시적인 함수를 통해 business를 제어할 수 있습니다.
현재는 파일크기가 크지않아 와닿지 않을 수 있지만 '컴포넌트를 가능한 작게유지하는것'에 대해서 고민이 필요할 수 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

뭔가 깔끔하게 된 것 같지는 않지만 커스텀 hook을 만들어 관심사를 분리했습니다.

commentLoadHandler();
}, [articleId, commentPost]);

if (!article) return <p>Loading article...</p>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

로딩처리를 위해 conditional 렌더링을 해주신것도 좋습니다!
다만 로딩이란 의미를 확인해보시면, API를 호출하고 네트워크를 통해 도착하지않은 Promise에 대해서 로딩처리를 해주셔야 한다는 점입니다. article은 조회했을때 0개 즉 서버요청에대한 응답값이 없을수도 있습니다. 이는 로딩상태가 아닌 no contents 상황일 수 있다는 점입니다.

async를 동작시키고 응답이오기전까지 를 로딩으로 생각해보신다면 로딩을 처리하는 더 좋은 방법이 존재할 것같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네 로딩 중일 때와 에러가 났을 때, 응답 값이 없을 때 3가지 경우로 수정했습니다.

return (
<div className={styles.ArticleDetailBox}>
<ArticleInfo article={article} />
<CommentPost articleId={articleId} commentPost={setCommentPost} />
Copy link
Collaborator

Choose a reason for hiding this comment

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

컴포넌트를 작게 파일단위로 분리해주신것도 아주 좋습니다.
위 리뷰와 이어서 피드백을 드린다면, 분리된 컴포넌트에서 여전히, article, commentPost에 대한 상태가 공통으로 사용해야하는 것 이기때문에 가장 상위에서 state를 관리해주신걸로 확인이 됩니다. 이는 리액트에서 자주 사용하게되는 presentational-container패턴으로 설명할 수 있는데요, 이는 명백한 단점이 존재하긴 합니다.

때문에 위에서 말씀드린것 처럼 (API호출을 분리한)business로직을 분리하는것을 설명드릴 수 있는데요, 분리된 business로직에서는 각각의 컴포넌트에서 재사용할 수 있습니다.

이렇게된다면 또다시 문제가 발생할 수 있는게, 컴포넌트 마다 API호출을 해야한다는 점이 있을수 있어요. 분리된 3개의 컴포넌트에서 서버로부터 받아와야 할 값이 필요하다면 3번 요청해야한다는 점 입니다. 이때바로 지금 학습하시는 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.

음.. 현재는 [id].js에서 article이든 comment이든 데이터를 불러와서 하위 컴포넌트로 내려주고 있는데 그렇게 하는 것보다 하위 컴포넌트 내에서 따로 따로 데이터를 불러오는 게 좋고 그랬을 때 나올 수 있는 문제 점을 react-query가 보완해준다는 말씀이시죠?
깊이가 부족해서 아직 완전히 이해가 되진 않지만 react-query를 앞으로 써보면서 이해가 될 것 같습니다.
일단 알고 넘어가겠습니다.

근데 presentational-container패턴의 단점에 대해서는 아직 잘 몰라서 언젠가 설명을 해주셔도 좋을 것 같네요.

<button className={styles.toCommunityFeedButton}>
목록으로 돌아가기
<div className={styles.toCommunityFeedButtonImage}>
<Image
Copy link
Collaborator

Choose a reason for hiding this comment

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

next/image를 사용해주신것도 좋습니다!
nextjs가 이미지 컴포넌트를 따로 제공하는이유, 최적화하는 방식과 종류를 나중에 설명할 수 있으면 좋을것 같네요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네!

}
};

return (
Copy link
Collaborator

Choose a reason for hiding this comment

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

이러한 종류의 UI는 form형태로 구성해주시는것이 좋습니다. HTML의 form은 기본기능만으로도 제공되는게 많은데요, 그이점을 충분히 사용할 수 있기 때문입니다.

또한 현재 작성해주신 컴포넌트기준으로 상태를 관리하는것을 본다면, 하나의 input에서 이벤트가 발생할 때마다 나머지 input도 모두 리-렌더링이 되는걸 확인할 수 있습니다. 이런부분이 리액트에서 최적화를 고민하게 되는 부분이기도 합니다. 내가 원하는 곳만 리-렌더링 시키는 방식을 말이죠.

'컴포넌트를 가능한 작게 유지' 하는것이 상태를 원하는곳만 사용하도록 세팅하는 것 이고, 그렇기때문에 form으로 작성될때의 이점이 있을 수 있다는점 다시한번 설명드립니다.

Copy link
Collaborator Author

@Jin-coding-333 Jin-coding-333 Dec 14, 2024

Choose a reason for hiding this comment

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

네 일단 인지하겠습니다!
sprint9 미션을 하면서 한번 적용해봐야 겠네요.

const [textareaValue, setTextareaValue] = useState("");

const handlePostComment = async (e) => {
e.preventDefault();
Copy link
Collaborator

Choose a reason for hiding this comment

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

e.preventDefault()는 javascript의 이벤트의 기본동작을 막거나 이벤트의 전파를 막는데 사용됩니다. 때문에 사용한 이유가 명확해야하는데요, 코멘트 확인하시면 해당 로직을 작성한 이유를 답변해주시면 좋을것 같습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

강의 보고 적용하기 바빠서요.. 보고 적용한다고 했는데 다시 확인해보니 form 태그의 기본 동작인 쿼리 스트링과 함께 이동하는 것을 막기 위해 썼던 코드네요..ㅎ 필요 없는 코드라 관련된 모든 코드를 수정했습니다!

@@ -0,0 +1,59 @@
import Link from "next/link";
Copy link
Collaborator

Choose a reason for hiding this comment

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

header와 footer를 컴포넌트형태로 분리해주신것 좋습니다. 다만 넣어주신 디렉토리 이름이 ui인데 좀더 명확한 이름(layouts?)으로 사용되는것이 좋을 수 있습니다.

또한 이러한 정적 컴포넌트들은 next의 SSR SSG형태로 제공하는것을 고민해보시는것이 필요할 수 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

디렉토리 이름은 layouts으로 바꿨습니다. 더 명확한 이름이 있는지 몰랐네요!

Header와 Footer와 같은 지극히 정적인 컴포넌트에 대해 왜 SSR이나 SSG형태를 고민해야 할까요?
기본적으로 Header와 Footer는 SSG로 생성되고 데이터를 fetch해야 하지도 않아서 괜찮은 거 아닌가요??
어떤 이유로 어떤 차이가 있어 SSR 또는 SSG 형태로 제공해야 하는지 궁금합니다.

@Jin-coding-333 Jin-coding-333 added 최종 제출 스프린트미션 최종 제출본입니다. and removed 미완성🫠 완성은 아니지만 제출합니다... 진행 중 🏃 스프린트 미션 진행중입니다. labels Dec 14, 2024
@kimjong95 kimjong95 merged commit cf23b79 into codeit-sprint-fullstack:next-배진한 Dec 15, 2024
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.

2 participants