Skip to content

Conversation

@BeanMouse
Copy link

@BeanMouse BeanMouse commented Mar 18, 2025

배포

느낀점

바닐라로 구현했을 때보다 오히려 state 관리하는 리엑트에 더 많은 시간을 투자한 것 같은 느낌이 듭니다... 보통 반대인데 어디서부터 잘못된 것 일까요? state를 구현할 때 비동기가 너무너무너무 ... 힘들었습니다. state가 변하는 알맞은 때를 구분하지 못해 계속해서 콘솔을 통해 바뀌었는지 안 바뀌었는지 알아보곤 했습니다.
그리고 캘린더 구현하는 것도 너무 어려웠습니다. 앞으로는 외부 라이브러리를 꼭 쓰고 싶다는 생각을 했습니다. 하지만 커스텀이 역시 style을 커스텀하기 편하다는 생각이 드는 것도 동시였습니다. 역시 튜닝의 끝은 순정이랄까...?
그리고 앞으로는 조금 더 기본을 튼튼하게 하기 위해 리엑트나 모던 자바스크립트 관련 책을 보아 더 열심히 공부 해야겠다는 생각이 들었습니다. 최적화랑 함수 이름 짜는 것도 어렵고 과제를 빨리 하고 공식 문서나 책을 좀 보는 시간을 가질려합니다 ㅠㅠ

혹시 최적화 쪽으로 피드백 주시면 너무너무 감사할것 같습니다!!

Key Question

  1. Virtual DOM

    1-1. 개념
    React에서 UI를 효율적으로 사용하기 위해 사용하는 개념이며 실제 DOM을 변경하는 대신 메모리 내에 가상 돔을 만들어 최소한의 변경사항만 실제 DOM에 적용시키는 것이다.

    1-2. 사용하는 이유

    • 성능 최적화: 브라우저의 불필요한 돔 조작을 줄여 랜더링 성능을 높임 (repaint,reflow 방지)

    • 빠른 업데이트 및 효율적 변경: Virtual 돔에서 변경 사항만 가져와 최소한의 변경 사항만 실제 DOM에 반영

    • 불변성 유지: React는 UI 상태를 불변 상태로 유지해서 변경 사항을 쉽게 비교 가능( 기존 객체 직접 수정 X, 새로운 객체 생성)

  2. 리엑트 랜더링 최적화 설명

    2-1. React.Memo

    • parent Components가 리랜더링되면 child Components는 불필요하게 리랜더링 되는데 이를 방지하기 위한 방법

    2-2. useMemo

    • 값을 저장해서 의존하는 값이 변경되지 않으면 기존의 값을 재사용하는것
      const result = useMemo(() =>countFunc (number), [number]);

    2-3 useCallBack

    • 함수를 저장해서 의존하는 값이 변하지 않으면 기존 함수 재사용
    const handleClick = useCallback(() => {
      console.log("버튼 클릭");
    }, []);
    

3.React 컴포넌트 생명주기

  • 마운트 -> 업데이트 -> 언마운트

  • 처음 화면에 나타날때 (마운트�) -> props의 변경 등으로 재랜더링(업데이트) -> 화면에서 사라질 때 (언마운트)

    3-1. Mount

    • constructor() 초기 state 결정
    • render()UI 랜더링
    • componentDidMount() → 마운트된 후 실행
      -함수형 컴포넌트: useEffect 의존형 빈 배열일 때

    3-2. UpDate

    • componentDidUpdate(prevProps, prevState) → 이전 값과 비교하여 변경 사항 처리 가능
    • 컴포넌트 내에서 변화가 생길때 마다 리랜더링
    • 함수형 컴포넌트: useEffect에서 의존형 배열
    • API 호출, 이벤트 리스너 등록 같은 초기 작업을 수행

    3-3. UnMount

    • componentWillUnmount() -> 언마운트 시 실행
    • 컴포넌트가 사라질 때 실행
    • 함수형 컴포넌트: useEffect에서 정리 코드

Copy link
Collaborator

@yyj0917 yyj0917 left a comment

Choose a reason for hiding this comment

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

2주차 과제하느라 고생많으셨습니다! 어떻게 이리 빨리 제출하실 수 있는지 늘 감탄이 나옵니다. 제출이 빠름에도 코드나 프로젝트 환경 등등 고민이 묻어나는 부분들이 많았기에 더 좋았던 것 같습니다. React로 넘어가고, 앞으로 Next.js로 넘어가면서 렌더링 최적화를 많이 고민하게 될 것입니다! 최신 기술이다보니 다양한 렌더링 최적화 기법이 있는데 과제, 스터디를 진행하며 많이 고민하고, 경험하시면 좋을 것 같습니당. 추가적으로 컴포넌트를 기능과 목적, 필요에 맞게 분리하는 것 + 함수명(이거는 저도 항상 고민합니다), 코드 가독성 등 리액트 개발의 숙제와도 같은 부분들도 앞으로 과제 진행하시면서 더 고려해보시면 좋을 것 같아요! 고생많으셨습니다~~ 🥇 🥇 👍 👍 💯 💯 🍏 🍓 🍑

++ 과제를 구현하며 코드를 더 깊게 바라보고, 내가 기존의 생각했던 코드가 아닌 다른 방향도 여러번 고민해보면 더 좋은 코드와 결과물이 나올 수도 있을 것 같아요! 늘 파이팅입니다! 😬

Copy link
Collaborator

Choose a reason for hiding this comment

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

type을 담거나 utils 함수들을 사용하는 파일은 .tsx확장자가 아닌 .ts확장자로 하는 게 추후 구분이 더 쉽습니다! 모든 파일을 tsx확장자로 했을 때 혼동이 생길 수 있으니 앞으로 jsx구문을 쓰는 것은 .tsx, 타입정의, 유틸 등의 로직은 .ts로 하는 것을 권장드립니다!

}
export interface TodoDataInfo {
//날짜별 할 일 데이터 타입
[key: string]: TodoDataItemInfo[];
Copy link
Collaborator

Choose a reason for hiding this comment

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

알아보니 '인덱스 시그니처'라는 기능이네요. 배열에서 속성으로 key: string과 같은 아무 문자열을 받을 수 있도록 하는 동작이네용. 신기합니다

))}
</ul>
<AddTodoContainer>
<AddInput
Copy link
Collaborator

Choose a reason for hiding this comment

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

react에서는 dom으로 input상태를 관리하기보다 input을 Controlled Component(제어할 수 있는 - inputValue를 state로 관리하여 제어할 수 있는 컴포넌트 등등)로 관리하는 것이 더 좋아보입니다.

`const [inputValue, setInputValue] = useState("");

<AddInput
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
/>` 요런 느낌입니다!

추가적으로 keydown eventlistner을 useEffect를 활용해 전역에 등록중인데 그랬을 때 지금은 아니지만 다른 input요소를 활용할 때 원치 않은 동작이 발생할 수 있으니 해당 input의 onKeyDown로 넣어 내가 관리하고자 하는 input을 정해서 관리하는 게 좋을 것 같아요

const modalRoot = document.getElementById("modal-root"); //id가 modal-root인 요소를 찾아서 modalRoot에 할당
if (!modalRoot) return null; //modalRoot가 없으면 null 반환

return ReactDOM.createPortal(
Copy link
Collaborator

Choose a reason for hiding this comment

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

createPortal을 활용하여 ui 레이어를 분리하셨네요! 찾아봤는데 굉장히 매력적인 방식인 것 같네요

const [selectedDate, setSelectedDate] = useState(""); //선택한 날짜 상태 관리
return (
<>
<CalendarContainer>
Copy link
Collaborator

Choose a reason for hiding this comment

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

캘린더를 직접 구현하시다니...손이 진짜 빠르신 것 같아요. 확실히 구현할 게 많은 것 같은데 styled-component만을 활용하는 게 아닌 React component도 활용하면 좋을 것 같아요. 기능과 목적에 맞게 CalendarTitle, CalendarWeeks(월~일), CalendarDays 등으로 컴포넌트를 분리한다면 각 컴포넌트에 해당하는 변수들도 분리가 될테니 가독성이 더 좋아질 것 같아요

);
if (isNotDoneCount === 0) return { completeMessage: `할 일 다함!` };
return {
incompleteMessage: `할 일 남음!`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

CamelCase! 🌵

<Title>
{date.getFullYear()}년 {date.getMonth() + 1}월
</Title>
<CalendarDays>
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
Author

Choose a reason for hiding this comment

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

ㅎ... 넹

<Today>오늘</Today>
) : (
<CalendarDate>
<HighlightDate
Copy link
Collaborator

Choose a reason for hiding this comment

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

HighlightDate보다 todo가 있는 날짜이니 HasTodoDate같은 이름은 어떨까 한번 생각해봅니다 👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

이거 svg 많이 탐나요

Copy link
Author

Choose a reason for hiding this comment

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

구글링 1시간동안 햇씁니다..

))}
</CalendarDates>
</CalendarContainer>
{isModalOpen && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

calendar의 42칸 정도는 큰 영향은 없는데 한번 생각해보면 좋을 것 같아서 말씀드립니다! 리액트는 해당 컴포넌트의 state(ex. isModalOpen)가 변경되면 변경된 state를 가지는 컴포넌트는 재렌더링되는 특성이 있습니다. 그래서 isModalOpen으로 모달이 오픈되면 Calendar 컴포넌트 자체가 재렌더링됩니다. 이때 CalendarDateContents, Days 등 변하지 않는 부분들은 React.memo와 같이 메모제이션 기능을 활용하여 재렌더링 비용을 줄이는 방법으로 렌더링 최적화를 고려해볼 수 있을 것 같아요!~

보니까 필요가 없네용
Copy link

@xseojungx xseojungx left a comment

Choose a reason for hiding this comment

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

어떻게 이렇게 빨리하시나요???? 손 진짜 빠르시네요... 코드 보고 많이 배워갑니다! 수고하셨어용

Choose a reason for hiding this comment

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

(아마도) 안 쓰는 vite 기본 요소들을 코딩 시작 전에 지워주시는것도 좋을 것 같아요!

const modalRoot = document.getElementById("modal-root"); //id가 modal-root인 요소를 찾아서 modalRoot에 할당
if (!modalRoot) return null; //modalRoot가 없으면 null 반환

return ReactDOM.createPortal(

Choose a reason for hiding this comment

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

createPortal에 대해서 새로 알고 갑니다!


function App() {
return (
<>

Choose a reason for hiding this comment

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

app.tsx파일에 페이지를 구현하기보다는 src/pages 폴더 만들어서 컴포넌트 끌어오는 페이지를 만든다면 나중에 서비스 사이즈가 커졌을 때 더욱 효율적으로 관리할 수 있을 것 같습니다!

@@ -0,0 +1,112 @@
import { useEffect, useState } from "react";
import {

Choose a reason for hiding this comment

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

저는 styled component 를 사용할 때 각각 요소를 import 하는 방식은 import 코드가 꽤나 길어지고 귀찮더라고요.. 그래서 's.dot' 방법을 주로 사용해요!

코드를 import * as s from '../...' 라고 쓴 후 각 스타일드 컴포넌트 사용하고 싶을 때 이 아닌 <s.AddButton/>으로 사용하면 됩니다! 그러면 진짜 컴포넌트/스타일드 요소 간의 구분도 더 잘 돼서 가독성도 증가하고 개인적으로 훨씬 편하다고 느꼈어요!

};

//날짜 변환 함수(미리 선언)
function transDate(calandarDate: number, index: number) {

Choose a reason for hiding this comment

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

function과 const로 선언한 화살표 함수를 사용하시는 기준은 어떻게 되시나요? 코드에서 const 화살표 함수와 function을 섞어 사용하신 이유가 궁금합니다!

Choose a reason for hiding this comment

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

그리고 계산 로직이 포함된 함수이니 utils 폴더에 따로 빼놓는것도 괜찮을 것 같아요!

flex-wrap: wrap;
`;

export {

Choose a reason for hiding this comment

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

이렇게 export를 따로 아래에 작성하신 이유가 있나요? 저는 export const Calender= ... 형태로 작성해서 궁금합니다!

Choose a reason for hiding this comment

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

image image 한번도 클릭 안 한 날짜를 클릭하면 투두를 입력하기 전에 빈 배열이 생기는 것 같아요! 투두 입력 후에 배열이 생기는 것이 아닌, 날짜 클릭하면 빈 배열이 생기게 만드신 이유가 궁금합니다!

}
};
useEffect(() => {
document.addEventListener("keydown", handleKeyDown);

Choose a reason for hiding this comment

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

handleKeyDown은 함수 내부에서 선언되었고, 의존성 배열에 들어가면 매 렌더링마다 리스너를 새로 등록/제거하는 문제가 발생할 것 같아요! useCallback으로 감싸서 메모이제이션하거나, 그냥 onKeyDown을 에 직접 주는 방식이 나을 것 같아요!
<AddInput onKeyDown={(e) => { if (!e.isComposing && e.key === "Enter") { handleAddTodo(); } }} />

},
{ isDoneCount: 0, isNotDoneCount: 0 }
);
if (isNotDoneCount === 0) return { completeMessage: `할 일 다함!` };

Choose a reason for hiding this comment

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

여기에서 리턴값이 상황에 따라서 다른 것 같은데 타입스크립스에서 반환 타입을 지정해서 통일하는게 좋을 것도 같아요! 저도 js 쓰다가 ts 쓰려니 너무 성가시고(?) 귀찮지만... 최대한 타입스크립트만의 이점을 최대한 살리려고 노력한답니다..ㅎㅎ

따라서
interface TodoCountResult { isComplete: boolean; done: number; notDone: number; }
이렇게 만들어 놓고
랜더링 부분에서
{count?.isComplete ? <CompleteStamp /> : ( <> <CountMessage>완료 {count.done}</CountMessage> <CountMessage>미완료 {count.notDone}</CountMessage> </> )}
이렇게 조건부 처리해주는게 일관성과 코드 가독성을 조금 더 높일 수 있을 것 같습니다!


//할 일 추가 이벤트 핸들러
const handleAddTodo = () => {
const inputEl = document.getElementById("addDataInput") as HTMLInputElement;

Choose a reason for hiding this comment

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

공식 문서
블로그
리액트에서는 getElementById보다는 useRef를 사용하라고 안내하더라고요! 관련 자료 첨부합니다.

@xseojungx
Copy link

오왁 슬랙에서 코드리뷰 해야하는 사람 잘못 봤네요ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 수면부족 이슈.... 그냥 지나가는 사람이라고 생각해주세요......

Copy link

@only1Ksy only1Ksy left a comment

Choose a reason for hiding this comment

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

과제를 진짜 빨리 하시는 것 같아요... 저번주도 그렇고 대단하십니당 코드리뷰 하면서 새로운 개념도 배워갑니다!! 과제 하시느라 수고 많으셨습니다! ㅎㅎ

{ length: 6 - currentLastDate.getDay() },
(_, i) => i + 1
);
const calendarDates = [

Choose a reason for hiding this comment

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

날짜 값들 중 date가 바뀌지 않는 이상 계산할 필요가 없는 것들은 useMemo로 최적화할 수 있지 않을까 싶습니당

const modalRoot = document.getElementById("modal-root"); //id가 modal-root인 요소를 찾아서 modalRoot에 할당
if (!modalRoot) return null; //modalRoot가 없으면 null 반환

return ReactDOM.createPortal(

Choose a reason for hiding this comment

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

createPortal을 활용한 코드를 처음 보는데 덕분에 하나 공부했습니다!!


//할 일 추가 이벤트 핸들러
const handleAddTodo = () => {
const inputEl = document.getElementById("addDataInput") as HTMLInputElement;

Choose a reason for hiding this comment

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

다른 분들도 남겨주셨는데 getElementById의 DOM 직접 접근 방식보다는 리액트의 다른 방식들을 이용하면 좋을 것 같아요!

`;
const HighlightDate = styled.div<{ haveTodo: boolean }>`
font-size: 1rem;
@media (max-width: 768px) {

Choose a reason for hiding this comment

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

곳곳에 반응형 적용하신 게 섬세하신 것 같습니당

Copy link

@DefineXX DefineXX left a comment

Choose a reason for hiding this comment

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

전체적으로 로직이 깔끔하고 귀염뽀짝한 UI까지 고생많으셨습니다 :)

Choose a reason for hiding this comment

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

styled-component들을 export로 사용하고, 이 파일 전체를 import * as S from './CalendarStyle.ts' 와 같이 *(asterisk)를 사용해 전체 export를 하는 것이 가독성 측면에서 더 좋을 것 같아요!

&:nth-child(7n) {
color: rgb(68, 0, 255);
}
${(props) => (props.condition ? `color:rgb(222, 222, 222) !important;` : ``)}

Choose a reason for hiding this comment

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

보통 !important는 다른 스타일링 파일 간의 우선 순위를 무시하고 적용되는걸로 알고 있는데, 이를 사용하지 않고 동일한 스타일을 적용할 수 있는 부분을 고민해보면 좋을 것 같습니다!

<>
<DateContainer
key={i}
condition={isOtherMonthDate(i)}

Choose a reason for hiding this comment

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

동적 스타일링의 목적으로 사용되는 props는 $condition와 같이 $을 붙여서 transient props로 사용하는게 styled-components 공식 문서에서 권장되는 방식이라고 알고 있는데, 이 부분 찾아보시면 좋을 것 같아요!

{calendarDates.map((calendarDate, i) => (
<CalendarDateContents
key={i}
i={i}

Choose a reason for hiding this comment

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

개인적으로 props의 이름과 변수 이름은 협업 시에 협업자가 한번에 이해할 수 있는 직관적인 네이밍을 사용하는게 좋다고 생각합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants