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

[우재현] Sprint10 #317

Conversation

Woolegend
Copy link
Collaborator

@Woolegend Woolegend commented Nov 30, 2024

요구사항

  • Github에 PR(Pull Request)을 만들어서 미션을 제출합니다.
  • 피그마 디자인에 맞게 페이지를 만들어 주세요.
  • 기존의 React, Typescript로 구현한 프로젝트와 별도로 진행합니다.
  • Next.js를 사용합니다

기본

게시글 등록 페이지

  • 게시글 등록 페이지 주소는 /addboard 입니다.
  • 게시판 이미지는 최대 한 개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 '등록' 버튼이 활성화 됩니다.

게시글 상세 페이지

  • 게시글 상세 페이지 주소는 /board/{id} 입니다.
  • 댓글 input 값을 입력하면 '등록' 버튼이 활성화 됩니다.
  • 활성화된 '등록' 버튼을 누르면 댓글이 등록됩니다

심화

게시글 등록 페이지

  • 회원가입, 로그인 api를 사용하여 받은 accessToken을 사용하여 게시물 등록을 합니다.
  • '등록' 버튼을 누르면 게시물 상세 페이지로 이동합니다.

주요 변경사항

  • 이미지를 서버에 업로드 할 순 없지만 미리보기 이미지는 확인 가능합니다.

스크린샷

멘토에게

  • 무작정 만들다 보니 코드가 많이 더러워 진 거 같습니다. 개선할 부분이 있으면 아낌없이 말씀해 주세요!
  • 프론트엔드측에서 토큰을 관리하는 방식을 잘 모르겠습니다. 예를 들어 제 코드상에서는 access, refresh 두 토큰 모두 session storage에 저장했는데, 이 때 발생할 수 있는 문제와 해결 방안 혹은 더 좋은 관습이 궁금합니다.
  • 셀프 코드 리뷰를 통해 질문 이어가겠습니다.

@Woolegend Woolegend self-assigned this Nov 30, 2024
@Woolegend Woolegend added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🫠 죄송합니다.. labels Nov 30, 2024
@Woolegend Woolegend force-pushed the Next-우재현-sprint10 branch from be6caa0 to 9e37071 Compare December 2, 2024 15:12
@Woolegend Woolegend removed the 미완성🫠 죄송합니다.. label Dec 2, 2024
Copy link
Collaborator

@arthurkimdev arthurkimdev left a comment

Choose a reason for hiding this comment

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

수고하셨습니다~ 👍

Comment on lines +46 to +54
const response = await axios({
method: "post",
url: "/articles",
data: {
content,
title,
image,
},
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

axios 사용할 때 현재 처럼 설정 값을 객체 통해 지정하고 있는데, 아래 처럼 메서드를 직접 사용하는 방식이 보다 간결하고 명확해서 변경하면 좋겠습니다. 😸
기능적 차이는 없어요~

await axios.post('/articles', params);

Comment on lines +31 to +32
const response = await axios.get(`/articles/${id}`);
return response.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 { data } = await axios.get<Article>(`/articles/${id}`);
return data;

}: SignUpParams): Promise<AuthResponse> {
const response = await axios({
method: "post",
url: "/auth/signUp",
Copy link
Collaborator

Choose a reason for hiding this comment

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

이러한 URL은 contants 디렉토리 내 api.ts 파일을 하나 만들어서 상수로 관리하면 좋겠어요.

url: AUTH_SIGN_UP_URL,

Comment on lines +16 to +25
// 기존 인터셉터 제거
if (interceptorId !== undefined) {
instance.interceptors.request.eject(interceptorId);
}

// 새로운 인터셉터 추가
interceptorId = instance.interceptors.request.use((config) => {
config.headers["Authorization"] = value;
return config;
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호 interceptors 아이디어 좋습니다. 👍

Comment on lines +3 to +21
interface GetCommentListByArticleId {
articleId: number;
limit: number;
cursor?: number;
}

async function getCommentListByArticleId({
articleId,
limit,
cursor,
}: GetCommentListByArticleId) {
const response = await axios.get(`/articles/${articleId}/comments`, {
params: {
limit,
cursor,
},
});
return response.data;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서 GetCommentListByArticleId 값이 지역 타입이기도 하고, 타입이라 예상되지만 이처럼 PascalCase 로 네이밍이 되게되면 혼란이 야기될 수 있어서 타입 혹은 인터페이스 뒤에 props 붙이는 방법도 있습니다. 즉 GetCommentListByArticleIdProps 이처럼요.

Comment on lines +17 to +23
useEffect(() => {
document.addEventListener("click", handler);

return () => {
document.removeEventListener("click", handler);
};
}, [handler]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

remove 처리 까지 굳!

Comment on lines +1 to +9
import { setInstanceHeaders } from "@/api/axios";

export default function setLogOut() {
sessionStorage.removeItem("user");
sessionStorage.removeItem("accessToken");
sessionStorage.removeItem("refreshToken");
sessionStorage.removeItem("birth");
setInstanceHeaders();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

물론 해당 코드가 임시코드지만, 이런 반복되는 코드를 작성할 땐 아래처럼 생각해볼 수 있어요!

const STORAGE_KEYS = ['user', 'accessToken', 'refreshToken', 'birth'] as const;

export default function setLogOut() {
  STORAGE_KEYS.forEach(key => sessionStorage.removeItem(key));
  setInstanceHeaders();
}

Comment on lines +17 to +32
export async function getServerSideProps(context: any) {
const { id } = context.params;

const article = await getArticle({ id });
const initComments = await getCommentListByArticleId({
articleId: id,
limit: 10,
});

return {
props: {
article,
initComments,
},
};
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

일반적으로 getServerSideProps 부분은 아래에 위치하고 ArticleDetail 컴포넌트가 상위로 올라오는 구조로 작성합니다!

export default function ArticleDetail({
  article,
  initComments,
}: {
  article: Article;
  initComments: CommentList;
}) {

...

}

export async function getServerSideProps(context: any) {
  const { id } = context.params;

  const article = await getArticle({ id });
  const initComments = await getCommentListByArticleId({
    articleId: id,
    limit: 10,
  });

  return {
    props: {
      article,
      initComments,
    },
  };
}

Comment on lines +123 to +132

interface CommentFormProps {
value: string;
onChange: (value: string) => void;
onSubmit: () => void;
}

function CommentForm({ value, onChange, onSubmit }: CommentFormProps) {
const [valid, setValid] = useState(false);

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 +82 to +120
return (
<>
<header className={styles.header}>
<div className={styles.wrap}>
<h2 className={styles.title}>{article.title}</h2>
<Dropdown />
</div>
<div className={styles.wrap}>
<div className={styles.info}>
<div className={styles.profile}>
<Image fill src="/images/profile.svg" alt="프로필" />
</div>
<span className={styles.writer}>{article.writer.nickname}</span>
<span className={styles.createdAt}>
{formatDate(article.createdAt)}
</span>
</div>
<div className={styles.likeCount}>
<div className={styles.heart}>
<Image fill src="/images/ic_heart.svg" alt="좋아요" />
</div>
<span>{article.likeCount}</span>
</div>
</div>
</header>
<main className={styles.content}>{article.content}</main>
<CommentForm
value={commentValue}
onChange={handleChangeComment}
onSubmit={handleSubmitComment}
/>
<CommentListWrap comments={comments} />
<Link href="/board" className={styles.buttonBack}>
<span>목록으로 돌아가기</span>
<div className={styles.iconBack}>
<Image fill src="/images/ic_back.svg" alt="프로필" />
</div>
</Link>
</>
Copy link
Collaborator

Choose a reason for hiding this comment

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

꼭 재사용이 안되더라도, 현재 파일 기준으로는 가독성과 유지보수를 위해 컴포넌트를 분리하는 방법이 더 좋겠어요.
추가적으로 예시를 들자면, DropDown 컴포넌트에서만 사용하는 useOutsideClick hook도 분리된 컴포넌트에서 호출하는게 더 좋겠어요.


 const { comments, handleComment } = useComment(article.id, initComments);


return (
  <>
    <Components />
    <Components />
    <Components />
    <Components />
  </>
)

@arthurkimdev arthurkimdev merged commit 0dc5f30 into codeit-bootcamp-frontend:Next-우재현 Dec 10, 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