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

[이은빈] sprint9,10 #692

Conversation

eunbinnie
Copy link
Collaborator

요구사항

기본

[sprint9]

  • 전체 게시글에서 드롭 다운으로 “최신 순” 또는 “좋아요 순”을 선택해서 정렬을 할 수 있습니다.

[sprint10]

  • 게시글 등록 페이지 주소는 “/addboard” 입니다.
  • 게시판 이미지는 최대 한개 업로드가 가능합니다.
  • 각 input의 placeholder 값을 정확히 입력해주세요.
  • 이미지를 제외하고 input 에 모든 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
  • 게시글 상세 페이지 주소는 “/board/{id}” 입니다.
  • 댓글 input 값을 입력하면 ‘등록' 버튼이 활성화 됩니다.
  • 자유게시판 페이지에서 게시글을 누르면 게시물 상세 페이지로 이동합니다.

심화

[sprint9]

  • 반응형으로 보여지는 베스트 게시판 개수를 다르게 설정할때 서버에 보내는 pageSize값을 적절하게 설정합니다.
  • next의 data prefetch 기능을 사용해봅니다.

주요 변경사항

  • axios로 변경

스크린샷

image

멘토에게

@eunbinnie eunbinnie requested a review from kiJu2 June 14, 2024 13:01
@eunbinnie eunbinnie added 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. 미완성🫠 죄송합니다.. labels Jun 14, 2024
@kiJu2
Copy link
Collaborator

kiJu2 commented Jun 17, 2024

수고 하셨습니다 ! 스프리트 미션 하시느라 정말 수고 많으셨어요.
학습에 도움 되실 수 있게 꼼꼼히 리뷰 하도록 해보겠습니다.

Comment on lines +3 to +8
export interface GetArticlesQuery {
page?: number;
pageSize?: number;
orderBy: "recent" | "like";
keyword?: string;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 타입은 특정 API에서 필요한 타입같아 보여요 !

해당 파일에서 export 한다면, lib/axios.ts의 역할이 무엇인지 헷갈릴 것 같아요.
제가 보기에는 라이브러리 중에서 axios의 인스턴스를 반환하는 파일로 보입니다.

만약 제가 생각한 역할이 맞다면 GetArticlesQuery는 별도의 파일로 구분하는게 어떨까요 ?

Comment on lines +3 to +5
const addboard = () => {
return <div>상품 등록 페이지</div>;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

일반적으로 컴포넌트는 파스칼 케이스로 작성됩니다 ! 😊😊😊

Suggested change
const addboard = () => {
return <div>상품 등록 페이지</div>;
};
const AddBoard = () => {
return <div>상품 등록 페이지</div>;
};

Comment on lines +36 to +41
const setBestPageSize = (category: string) => {
if (category === "S") return 1;
else if (category === "M") return 2;
else if (category === "L") return 3;
else return 0;
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

set은 데이터를 업데이트할 때 주로 사용되는 용어입니다 !:

Suggested change
const setBestPageSize = (category: string) => {
if (category === "S") return 1;
else if (category === "M") return 2;
else if (category === "L") return 3;
else return 0;
};
const getBestPageSize = (category: string) => {
if (category === "S") return 1;
else if (category === "M") return 2;
else if (category === "L") return 3;
else return 0;
};

get이 좀 더 맞는 말이지 않을까 싶어요 😊

Copy link
Collaborator

Choose a reason for hiding this comment

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

그리고 로직을 수행하고 있지 않는데, 함수로 선언할 필요가 없을 것으로 보여요:

Suggested change
const setBestPageSize = (category: string) => {
if (category === "S") return 1;
else if (category === "M") return 2;
else if (category === "L") return 3;
else return 0;
};
const bestPageSizeMap = {
S: 1,
M: 2,
L: 3,
}

그리고 사용할 때는 다음과 같이 사용해요:

  bestPageSizeMap[category] || 0;

Comment on lines +173 to +176
const bestArticlesRes = await axios.get("/articles", {
params: bestOption,
});
const articlesRes = await axios.get("/articles", { params: option });
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 병렬로 처리할 수 있습니다:

Suggested change
const bestArticlesRes = await axios.get("/articles", {
params: bestOption,
});
const articlesRes = await axios.get("/articles", { params: option });
const [bestArticlesRes, articlesRes] = await Promise.all([axios.get("/articles", { params: option }), axios.get("/articles", { params: bestOption })]);

이렇게 병렬로 처리하게되면 성능이 훨씬 좋아집니다 ~!
지금 로직에서는 이 전에 처리를 기다릴 필요가 없으므로 병렬로 처리하는게 나을 거로 보이네요 😊

Comment on lines +12 to +13
const Dropdown = (props: PropsType) => {
const { list, setOption } = props;
Copy link
Collaborator

Choose a reason for hiding this comment

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

다음과 같이 구조분해 할당을 할 수 있습니다. 😊:

Suggested change
const Dropdown = (props: PropsType) => {
const { list, setOption } = props;
const Dropdown = ({ list, setOption }: PropsType) => {

Comment on lines +13 to +17
/**
* 커스텀 링크 함수 (링크 active 체크)
* @param activePaths 활성화되어야하는 경로 배열
* @returns Link 태그 반환
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

오호 jsdoc을 사용해보셨군요 👍 😊😊

Comment on lines +22 to +34
useEffect(() => {
const event = () => {
const width = window.innerWidth;
const category = getWindowSizeCategory(width);
setWindowWidth(category);
};

event();
window.addEventListener("resize", event);
return () => {
window.removeEventListener("resize", event);
};
}, [windowWidth]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

디바운싱/쓰로틀링을 통하여 성능 최적화를 시켜볼까요?

해당 이벤트에 console.log를 호출해보면 리사이징이 될 때마다 정말정말 많은 호출하는 것을 볼 수 있을거예요 !
그만큼 성능에 좋지 못하다는 이야기겠지요?
따라서, 프론트엔드 개발자들은 이렇게 잦은 이벤트가 발생할 때(리사이징, 스크롤, 타이핑 등) 디바운싱/쓰로틀링을 통하여 최적화를 시키곤 합니다.

쓰로틀링(Throttling): 일정 시간 동안 하나의 함수만 호출되도록 하는 기법입니다. 예를 들어, 사용자가 스크롤을 할 때, 매번 이벤트를 처리하지 않고 일정 간격으로 한 번만 처리하게 합니다. 이를 통해 성능을 향상시킬 수 있습니다.

디바운싱(Debouncing): 여러 번 발생하는 이벤트 중 마지막 이벤트가 발생한 후 일정 시간이 지난 다음에 한 번만 실행되도록 하는 기법입니다. 예를 들어, 사용자가 검색어를 입력할 때, 입력이 끝난 후 일정 시간 동안 추가 입력이 없으면 검색 요청을 보냅니다.

다음과 같은 코드가 있습니다:

function Container() {
    useEffect(() => {
      window.addEventListener('resize', handleResize);
      return () => {
        window.removeEventListener('resize', handleResize);
      }, []);
// ... Some code
}

디바운싱으로 다음과 같이 최적화를 시킬 수 있습니다 !:

function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

function Container() {
  const handleResize = () => {
    console.log('Window resized');
  };

  useEffect(() => {
    const debouncedHandleResize = debounce(handleResize, 300);
    window.addEventListener('resize', debouncedHandleResize);
    return () => {
      window.removeEventListener('resize', debouncedHandleResize);
    };
  }, []);

  // ... Some code
}

이렇게 하면 연속된 이벤트가 끝난 후 0.3초마다 호출하게 되어 기존보다 훨씬 최적화된 기능이 될 수 있습니다 !

Copy link
Collaborator

Choose a reason for hiding this comment

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

매번 작성하기 번거로운가요?

그렇죠... 번거롭죠.. (사실 저도 GPT한테 맡긴 코드입니다 하하하핳ㅎ 그래서 될지는 장담못함..)
디바운싱, 쓰로틀링은 정말 흔한 기법이어서 참조할 수 있는 문서가 많습니다 !

useHooks

해당 라이브러리를 사용한 코드로 볼까요?

import { useRef, useState } from 'react';
import { useDebounceCallback } from 'usehooks-ts';

function useDebounceValue(initialValue, delay, options) {
  const eq = options?.equalityFn ?? ((left, right) => left === right);
  const unwrappedInitialValue = typeof initialValue === 'function' ? initialValue() : initialValue;
  const [debouncedValue, setDebouncedValue] = useState(unwrappedInitialValue);
  const previousValueRef = useRef(unwrappedInitialValue);

  const updateDebouncedValue = useDebounceCallback(setDebouncedValue, delay, options);

  // Update the debounced value if the initial value changes
  if (!eq(previousValueRef.current, unwrappedInitialValue)) {
    updateDebouncedValue(unwrappedInitialValue);
    previousValueRef.current = unwrappedInitialValue;
  }

  return [debouncedValue, updateDebouncedValue];
}

export default useDebounceValue;

그리고 사용 방법:

import React, { useState } from 'react';
import useDebounceValue from './useDebounceValue';

const ExampleComponent = () => {
  const [inputValue, setInputValue] = useState('');
  const [debouncedValue] = useDebounceValue(inputValue, 500);

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <div>
      <input type="text" value={inputValue} onChange={handleChange} placeholder="Type something..." />
      <p>Debounced Value: {debouncedValue}</p>
    </div>
  );
};

export default ExampleComponent;

Comment on lines +4 to +6
export interface ChildrenProps {
children: React.ReactNode;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 타입은 React에서 PropsWithChildren와 유사해보여요 !

만약 원하시는 타입이 맞다면 재선언할 필요 없이 PropsWithChildren를 사용해보는 것도 고려해보면 좋겠군여 !

type PropsWithChildren<P = unknown> = P & { children?: ReactNode | undefined };

@@ -0,0 +1,15 @@
import { InputPropertyType } from "@/src/types/common.interface";
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 컴포넌트가 사용될 Props 타입이라면..

해당 파일 내부에 선언하는건 어떨까요?
InputPropertyType을 재사용 하고 싶어서 중앙에서 관리하고자 한 것 같아요.

(이어서)

Comment on lines +4 to +13
const Input = ({ value, onChange, placeholder }: InputPropertyType) => {
return (
<input
value={value}
onChange={onChange}
placeholder={placeholder}
className=" text-gray-800 placeholder:text-gray-400 leading-[1.5] bg-transparent flex-1 focus:outline-0"
/>
);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

(이어서) 다음과 같이 작성할 수 있습니다:

Suggested change
const Input = ({ value, onChange, placeholder }: InputPropertyType) => {
return (
<input
value={value}
onChange={onChange}
placeholder={placeholder}
className=" text-gray-800 placeholder:text-gray-400 leading-[1.5] bg-transparent flex-1 focus:outline-0"
/>
);
};
const Input = ({ value, onChange, placeholder }: React.InputHTMLAttributes<HTMLInputElement>) => {
return (
<input
value={value}
onChange={onChange}
placeholder={placeholder}
className=" text-gray-800 placeholder:text-gray-400 leading-[1.5] bg-transparent flex-1 focus:outline-0"
/>
);
};

리액트에서 이미 정의된 타입이 있어요. 위처럼 사용하시면 onChange, type, value 등등 새로 선언할 필요 없습니다 😊

Copy link
Collaborator

Choose a reason for hiding this comment

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

다음은 버튼을 예로 든 컴포넌트예요 !:

import cn from 'classnames';
import { ButtonHTMLAttributes } from 'react';

interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'none';
}

export default function MelonButton({ className, variant, ...rest }: Props) {

위처럼 추가적으로 필요한 타입이 있다면 인터섹션 타입을 통하여 정의해볼 수 있어요 😊

@kiJu2
Copy link
Collaborator

kiJu2 commented Jun 17, 2024

수고 많으셨습니다 은빈님 !! ㅎㅎㅎ
매번 성장하는게 눈에 보이는군요 !! 즐겁게 리뷰하였습니다 ㅎㅎ
리뷰 중 궁금하신거 있으시면 사전 질의를 통해서 남겨주시거나 멘토링 미팅 때 질문주세요 😊😊😊

@kiJu2 kiJu2 merged commit cb36658 into codeit-bootcamp-frontend:Next.js-이은빈 Jun 17, 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