Skip to content

Conversation

baekseungsun
Copy link

@baekseungsun baekseungsun commented Sep 19, 2025

배포링크:
react-todo-22nd-bynzx8ent-baekseungsuns-projects.vercel.app

후기:
먼저 React를 배우기 앞서서 지난 주 코드리뷰를 하며 지난 주 과제로 제출한 코드가 비효율적이라는 생각이 들어 수정하는 과정을 거쳤다. 가장 많은 조언을 받았던 부분인 중복되는 코드를 하나의 function으로 만들어 사용했고 todo를 저장하는 방법도 list를 사용하는 방식으로 바꿨다.
React를 이용한 코딩은 처음이어서 구조를 파악하고 어떤 기능들이 있는지, 기존 Vanilla JS로 짠 코드와 어느 부분이 다른지를 파악하는 것에 시간을 많이 투자했다. 여러 자료와 예시를 찾아보고 AI툴에게도 물어봤지만 답변이 모두 조금씩 달라 헷갈리는 부분이 많았다. 과제를 일단 마친 시점에도 아직 완벽히 이해하지 못한 것 같고, 약점이 많은 결과물이라 생각되어 코드 리뷰를 통한 조언을 참고하여 수정하려 한다. 이해한 바는 React를 사용하면 state라는 개념이 생기고, 지정해둔 이벤트 핸들러에 따라 함수를 불러와 간편하게 state를 바꾼다는 것이다. 가령 투두리스트의 할 일을 끝마쳐 "done"버튼을 누르면, toggleDone이 실행되고 해당 함수가 실행됨에 따른 결과가 페이지에 반영된다. 다만 React를 어떻게 이용하는 것이 가장 효율적이고 파일을 어떻게 분리하는 것이 유리한지에 대해선 더욱 경험하고 배워야 할 것 같다.

Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?
Virtual-DOM이란 실제 보여지는 DOM이 그려지기 전 메모리 안에서 그리지만 화면에 나타나지는 않는 가상의 DOM이다. 이를 이용하는 이유는 state가 달라짐에 따라 바뀐 부분이 있다면 state 변화 전후의 DOM을 비교하여 변경된 부분만을 실제 DOM에 반영하기 위함이다. 화면의 작은 부분만이 변경되어도 DOM 전체를 변경하는 것은 비효율적이기에 Virtual-DOM을 이용하여 정말 변경된 부분만을 반영하는 것이다.

React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요. 다른 방식이 있다면 이에 대한 소개도 좋습니다.
React.memo(): State, context가 아닌 prop의 변화가 없다면 다시 렌더링 하지 않는다. props는 부모에서 자식으로 전달되는 것이기에 자식이 많은 list에서 효율적이다. 한 리스트의 대부분이 변치 않고 몇몇 부분만 변경되었을 때, 변경되지 않은 부분은 건너뛰고 변경된 부분에 한해서 리렌더하는 방식이다.

useMemo(): 계산된 값을 기억하고 계산을 매번 반복하지 않는 방식이다. 예를 들어 특정 기준에 따라 리스트를 정렬해야 할 때 정렬을 매번 하는 대신 그 값을 기억하고 재사용하는 것이다.

useCallBack(): 함수 레퍼런스를 메모하여 다시 렌더하는 것을 방지한다. 부모가 다시 렌더되어도 자식에게 내리는 핸들러가 그대로라면 useCallBack을 이용하여 자식이 리렌더를 건너뛸 수 있게 한다.

React 컴포넌트 생명주기에 대해서 설명해주세요.
생명주기는 컴포넌트가 생성되고 업데이트되고 제거되는 과정이다. 생성과정에선 컴포넌트를 DOM에 렌더하고, 업데이트 과정에서는 리렌더 후 virtual-DOM을 이용하여 변화된 부분을 DOM에 반영한다. 마지막 제거 과정에서는 DOM에서 분리되며 메모리에서도 해제된다.

@KWONDU KWONDU self-requested a review September 22, 2025 01:59
Copy link
Member

@KWONDU KWONDU left a comment

Choose a reason for hiding this comment

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

좋은 코드 감사합니다 -!

"semi": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 3
Copy link
Member

Choose a reason for hiding this comment

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

tabWidth를 3으로 설정하신 이유가 있을까요 ? 일반적으로 html, js 같은 프론트엔드 기술 스택에서는 2를 주로 사용합니다

Copy link
Member

Choose a reason for hiding this comment

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

prettier 설정하실 때도 각 속성이 어떤 역할을 하는지 명확히 이해하신 후 사용하시는 걸 추천드립니다.

"name": "assignment2",
"version": "1.0.0",
"description": "안녕하세요 🙌🏻 22기 프론트엔드 운영진 **권동욱**입니다.",
"main": "index.js",
Copy link
Member

Choose a reason for hiding this comment

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

package json 같은 기본적인 config 파일일지라도 각 속성이 어떤 역할을 하는지 간략하게나마 알아두시면 좋습니다. description property는 차치하고라도 index.js 파일이 존재하지 않는 것으로 보이네요

@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
Copy link
Member

Choose a reason for hiding this comment

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

추후 프로젝트 진행하시게 되면 높은 확률로 대상 국가가 대한민국일텐데요. 검색 엔진 최적화 등 다양한 요소를 고려하였을 때 lang="ko" 로 지정하시면 좀 더 완성도 높은 결과물이 나올 거 같아요

Copy link
Member

Choose a reason for hiding this comment

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

개인적으로는 이런 사소한 것 하나하나부터 확실히 이해하는 게 중요하다고 생각합니다

Copy link
Member

Choose a reason for hiding this comment

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

이 파일 1주차 과제 더미 데이터로 보이는 데 맞을까요 ?

Copy link
Member

Choose a reason for hiding this comment

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

3주차 과제부터는 코딩 중간중간 / 제출 직전에 폴더 구조나 파일 구성 점검해보는 습관을 들이는 걸 추천드립니다

Copy link
Author

Choose a reason for hiding this comment

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

엇 그렇네요 수정하고 옮기는 과정에서 만들어뒀는데 지우는 걸 잊었습니다. 감사합니다

Copy link
Member

Choose a reason for hiding this comment

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

이 파일도 불필요한 것 같네요



//Functions
const toDateKey = (d) => new Date(d).toLocaleDateString('en-CA'); // YYYY-MM-DD in local time
Copy link
Member

Choose a reason for hiding this comment

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

캐나다 시간대로 설정하신 이유가 있을까요 ?

onKeyDown={(e) => {
if (e.key === 'Enter') addTodo();
}}
/>
Copy link
Member

Choose a reason for hiding this comment

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

React에서 한글 입력 시 발생하는 고질적인 문제가 있는데요, IME(Input Method Editor) 이슈라고 합니다.

구체적으로 영어와 달리 한글은 자음과 모음을 조합해서 만들어지는 문자여서 text input에 한글 입력 후 엔터 누르시면 두 번 입력되는 걸 보실 수 있습니다. (Add 버튼 클릭 X, 무조건 Enter)

이를 해결하기 위해서 isComposing 속성을 사용할 수 있습니다. 3주차 과제가 메신저인 만큼 해당 부분 유의해주세요

}
},
[dateKey],
);
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 empty list일 때 해당 데이터 아예 제거하는 로직은 좋은 거 같아요

Choose a reason for hiding this comment

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

이 방식이 key를 없애서 필요한 배열만을 가지는 좋든 방식인 것 같지만 단순하게 빈 배열로 초기화해서 key값을 남기는 방식이 코드가 조금 더 쉽지 않을까 생각이 듭니다

* { box-sizing: border-box; }
body { margin: 0; font-family: Arial, sans-serif; background: #e2e8f0; color:#0f172a;}
button { cursor: pointer; }
`;
Copy link
Member

Choose a reason for hiding this comment

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

globa style 적용하시는 방법 자체는 좋은 것 같습니다. 다만 reset css나 normalize css 검색하시면 관련하여 정형화된 css 코드가 존재하는데요. 해당 코드 사용하시는 것을 추천드립니다.

대표적인 예시로 meyer's reset css가 있습니다

Copy link
Member

Choose a reason for hiding this comment

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

3주차부터는 tailwind CSS 사용하시게 될텐데, 미리 말씀드리면 tailwind CSS v4.0부터는 기본적으로 reset css가 내장되어 있습니다

persist(next);
},
[todos, dateKey, persist],
);
Copy link
Member

Choose a reason for hiding this comment

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

pin 기능 구현하신 건 좋은 거 같아요

Comment on lines +11 to +230
const Header = styled.header`
display: flex;
align-items: center;
justify-content: center;
background: #0f172a;
color: white;
padding: 14px 16px;
position: relative;
`;
const Title = styled.h1`
margin: 0;
font-size: 35px;
font-weight: 600;
`;
const MenuBtn = styled.button`
position: absolute;
left: 16px;
background: none;
border: none;
padding: 8px;
`;
const MenuIcon = styled.div`
width: 24px;
height: 2px;
background: white;
border-radius: 2px;
position: relative;
&::before,
&::after {
content: '';
position: absolute;
left: 0;
width: 24px;
height: 2px;
background: white;
border-radius: 2px;
}
&::before {
top: -10px;
}
&::after {
top: 10px;
}
`;

const Drawer = styled.aside`
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 300px;
background: #e2e8f0;
transform: translateX(${(p) => (p.open ? '0' : '-100%')});
transition: transform 0.25s ease;
z-index: 10;
`;
const DrawerInner = styled.nav`
display: flex;
height: 100%;
flex-direction: column;
gap: 8px;
font-size: 15px;
position: relative;
padding: 20px;
`;
const CloseBtn = styled.button`
position: absolute;
left: 20px;
top: 15px;
font-size: 30px;
width: 28px;
height: 28px;
background: none;
border: none;
`;
const DrawerOptions = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 110px;
margin-top: 80px;
`;
const WeekBtn = styled.button`
width: 120px;
height: 30px;
font-size: 16px;
border-radius: 10px;
`;

const DateRow = styled.div`
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin: 30px;
`;
const DateBtn = styled.button`
font-size: 20px;
background: none;
border: none;
`;
const TodayBtn = styled.button`
font-size: 20px;
background: none;
border: none;
color: #0f172a;
`;

const InputRow = styled.div`
display: flex;
justify-content: center;
gap: 20px;
margin: 40px auto;
background: #fff;
border-radius: 20px;
padding-left: 10px;
width: 400px;
height: 50px;
align-items: center;
`;
const TextInput = styled.input`
flex: 1;
font-size: 23px;
border: none;
outline: none;
background: none;
color: #0f172a;
`;
const AddBtn = styled.button`
font-size: 15px;
width: 60px;
height: 25px;
border-radius: 13px;
border: 2px solid black;
background: #0f172a;
color: white;
margin-right: 6px;
`;

const ListWrap = styled.div`
display: flex;
flex-direction: column;
gap: 6px;
height: 425px;
width: 600px;
margin: 0 auto;
background: whitesmoke;
border: 6px solid #0f172a;
border-radius: 20px;
`;
const TopRow = styled.div`
display: flex;
`;
const ClearBtn = styled.button`
color: #0f172a;
font-size: 20px;
border-radius: 20px;
width: 120px;
margin: 5px 0 0 10px;
background: #e2e8f0;
`;
const NumToDos = styled.div`
line-height: 1.5;
color: #0f172a;
font-size: 18px;
border-radius: 20px;
width: 120px;
margin-top: 5px;
margin-left: auto;
margin-right: 10px;
background: #e2e8f0;
border: 2px solid #0f172a;
padding-left: 20px;
`;

const UL = styled.ul`
list-style: none;
padding: 0;
margin: 5px;
max-height: 350px;
overflow: auto;
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
font-size: 26px;
`;

const LI = styled.li`
display: flex;
align-items: center;
gap: 8px;
padding: 10px 5px;
border: 3px solid #0f172a;
border-radius: 15px;
width: 550px;
background: #e2e8f0;
`;
const Txt = styled.span`
margin-left: 10px;
margin-right: auto;
font-size: 20px;
`;
const SmallBtn = styled.button`
border-radius: 20px;
font-size: 15px;
padding: 4px 10px;
border: ${(p) => (p.outline ? '1px solid black' : 'none')};
background: ${(p) => (p.solid ? '#0f172a' : 'none')};
color: ${(p) => (p.solid ? 'white' : '#0f172a')};
`;
const DoneBtn = styled(SmallBtn)`
background: ${(p) => (p.active ? 'grey' : '#0f172a')};
color: ${(p) => (p.active ? 'black' : 'white')};
`;

const PinBtn = styled(SmallBtn)`
background: ${(p) => (p.active ? 'crimson' : 'none')};
color: ${(p) => (p.active ? 'white' : '#0f172a')};
`;

Choose a reason for hiding this comment

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

css와 관련된 파일을 하나의 파일로 분리하면 가독성이 더 좋을 것 같아요

//전체삭제
const clearAll = useCallback(() => {
setByDate((prev) => ({ ...prev, [dateKey]: [] }));
sessionStorage.removeItem(dateKey);

Choose a reason for hiding this comment

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

key 자체를 지우는 것으로 보이는데, 다시 불러올때 key 값이 없는것이 문제가 될수도 있을 것 같아요 배열 값을 초기화하는 방식이 좋을 것 같아요

);
const total = todos.length;

// 첫 로드 시

Choose a reason for hiding this comment

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

React Hook을 적절하게 잘 사용한 것 같습니다

Comment on lines +259 to +286
useEffect(() => {
const raw = sessionStorage.getItem(dateKey);
if (raw) {
try {
const arr = JSON.parse(raw);
setByDate((prev) => ({...prev, [dateKey]: arr}));
} catch {
/* ignore */
}
} else {
setByDate((prev) => ({...prev, [dateKey]: []}));
}
}, []);

// 날짜 바뀜에 따라 롣드
useEffect(() => {
const raw = sessionStorage.getItem(dateKey);
if (raw) {
try {
const arr = JSON.parse(raw);
setByDate((prev) => ({...prev, [dateKey]: arr}));
} catch {
setByDate((prev) => ({...prev, [dateKey]: []}));
}
} else {
setByDate((prev) => ({...prev, [dateKey]: []}));
}
}, [dateKey]);

Choose a reason for hiding this comment

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

두 코드가 거의 일치하는데 두번째 useEffect만 있어도 충분할 것 같습니다

}
},
[dateKey],
);

Choose a reason for hiding this comment

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

이 방식이 key를 없애서 필요한 배열만을 가지는 좋든 방식인 것 같지만 단순하게 빈 배열로 초기화해서 key값을 남기는 방식이 코드가 조금 더 쉽지 않을까 생각이 듭니다

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.

3 participants