Skip to content

Conversation

@kkys00
Copy link

@kkys00 kkys00 commented Mar 22, 2025

배포 링크

https://react-todo-21th-smoky.vercel.app/

느낀점

  1. 그동안 css 파일을 통한 스타일링만 해봤고 스타일드 컴포넌트를 써본 적이 없어서 고민이 되었다. 스타일의 기능만 있는 컴포넌트와 기능이 함께 있는 컴포넌트의 역할이 다르다고 생각해서 고민 끝에 /src/styles/src/components 두 가지 디렉토리로 나눠서 작업했다.

    1. /src/styles: 특별한 기능과 로직이 없는, 스타일을 위한 컴포넌트들을 위치
    2. /src/components: 기능이 있는 컴포넌트로 /src/styles 위치
    • /src/styles 내부의 스타일드 컴포넌트를 최대한 재사용하려고 노력했다.
  2. 코드 리뷰 활동을 통해 isComposing을 알게 되어 적용해봤고, 평소에 스크린 사이즈를 가로로는 줄여도 세로로는 줄이지 않는 편이라 overflow: hidden을 설정하는 습관이 있었는데, 유저 행동에 여러가지 경우의 수를 생각해야 함을 느꼈다.

Key Question

1. Virtual-DOM은 무엇이고, 이를 사용함으로서 얻는 이점은 무엇인가요?

Virtual-DOM은 실제 DOM을 자바스크립트 객체로 흉내낸 가상의 메모리 내에 위치한 DOM 구조이다.

  1. React는 업데이트가 발생하면 실제 DOM을 직접 조작하는 대신 메모리에서 먼저 변경 사항을 계산하고 (Virtual-DOM에 먼저 반영하고),
  2. 여러 개의 상태 변경이 있을 때, 동시에 발생한 업데이트를 모아 최소한의 업데이트만 실제 DOM에 반영하기 때문에 렌더링 성능이 최적화된다.
  3. 변경된 부분만 찾아내 실제 DOM에 반영하기 때문에 전체 페이지를 다시 그리지 않고 부드럽게 UI가 갱신되고, 화면이 깜빡거리지 않는다.
    => 화면 업데이트가 빠르고 효율적으로 처리된다.

2. React.memo(), useMemo(), useCallback() 함수로 진행할 수 있는 리액트 렌더링 최적화에 대해 설명해주세요.

  1. Reac.memo() - 함수형 컴포넌트의 불필요한 리렌더링을 방지하는 기술이다. 부모 컴포넌트가 렌더링될 때, props가 변하지 않으면 자식 컴포넌트가 다시 렌더링되지 않도록 한다.
    const OptimizedChild = React.memo(ChildComponent)
    자식 컴포넌트를 인수로 받아 최적화된 컴포넌트로 만들어 반환해준다.

  2. useMemo() - 값의 계산을 메모이제이션하여 불필요한 연산을 방지하는 리액트 훅이다. 반복되는 동일한 연산을 매번 새롭게 수행하지 않고 결과값을 메모리에 저장한 후 꺼내오고 값이 바뀔 때만 계산을 다시 수행한다.

  3. useCallback() - 함수를 메모이제이션하여 자식 컴포넌트에 함수를 전달할 때 불필요하게 함수가 새로 생성되지 않도록 최적화하는 기법으로 함수가 props로 전달될 때 함수가 변경되지 않도록 보장해 자식 컴포넌트가 불필요하게 리렌더링되는 것을 방지한다.

3. React 컴포넌트 생명주기에 대해서 설명해주세요.

리액트 컴포넌트의 라이프 사이클은 Mount -> Update -> ... -> Update -> UnMount로 구성된다.

  1. Mount는 컴포넌트가 화면에 처음 렌더링 되는 시점이고
  2. Update는 컴포넌트가 상태 변화 등에 의해 리렌더링 되는 시기이다.
  3. UnMount는 컴포넌트가 화면에서 사라져 렌더링에서 제외된 시점이다.

kkys00 added 30 commits March 18, 2025 13:15
Copy link

@gustn99 gustn99 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 +3 to +19
/* 특정 일이 몇 째 주인지 계산하여 반환*/
const getWeekNum = (date) => {
const firstDayOfMonth = new Date(date.getFullYear(), date.getMonth(), 1)
const firstDayWeekday = firstDayOfMonth.getDay()
const dayOfMonth = date.getDate()

let month = date.getMonth() + 1
let week = Math.ceil((dayOfMonth + firstDayWeekday) / 7)

if (firstDayWeekday > 0 && firstDayWeekday < 5) return [month, week]

week -= 1
if (week > 0) return [month, week]

const lastDayOfPrevMonth = new Date(date.getFullYear(), date.getMonth(), 0)
return getWeekNum(lastDayOfPrevMonth)
}
Copy link

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.

감사합니다~!

Comment on lines +27 to +31
for (let i = curDay; i !== 0; i--) {
dateObj.setDate(dateObj.getDate() - 1)
weekDate.push(formatDate(dateObj))
}
weekDate.reverse()
Copy link

Choose a reason for hiding this comment

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

unshift 대신 push 후 reverse 하는 코드도 좋네요! 전반적으로 로직 구현을 잘 하시는 것 같아요. 많이 배워 갑니다 ㅎㅎ.

Copy link

@gustn99 gustn99 Mar 23, 2025

Choose a reason for hiding this comment

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

저는 useReducer를 거의 사용하지 않아 왔는데요, 처음에는 코드가 많이 낯설었지만 특정 state에 대해 정형화된 여러 변화가 예상되는 경우 작성하신 코드처럼 한 곳에서 로직을 모아 볼 수 있다는 점, 이벤트 핸들러를 contextAPI로 넘겨줄 때 캡슐화가 잘 된다는 점에서 좋은 선택인 것 같습니다.

그런데 두 가지 우려되는 점이 있습니다. 리액트는 state가 업데이될 때마다 리렌더링되고, 리렌더링될 때마다 함수를 재생성합니다. 따라서 todos가 업데이트되면서 App 컴포넌트가 리렌더링되고, 그에 따라 모든 이벤트 핸들러도 재생성됩니다. 또한 하위 모든 컴포넌트들이 리렌더링되며 성능 저하를 유발할 수 있겠죠.

두 번째는 contextAPI도 value가 변경되면 그 value를 소비하는 모든 컴포넌트의 리렌더링을 유발한다는 점입니다. 앞서 말씀드린 대로 todos와 이벤트 핸들러들이 모두 업데이트/재생성되기 때문에 App 컴포넌트의 리렌더링을 제외하고도 contextAPI의 value를 소비하는 모든 컴포넌트들의 리렌더링이 유발됩니다.

이런 문제를 해결하기 위해 React.memo와 useMemo, useCallback을 적절히 사용할 수 있습니다. 좋은 예제가 포함된 글을 발견해서 첨부해 드립니다!

Comment on lines +125 to +144
<GlobalStyle />
<Header />
<Wrapper>
<Container>
<WeeklyContentHeader />
<WeeklyViewer />
</Container>
<input
ref={dateInputRef}
onChange={onChangeDate}
type="date"
hidden
/>
<Container width="50%">
<DailyContentHeader />
<Editor />
<TodoViewer />
</Container>
</Wrapper>
<Footer />
Copy link

@gustn99 gustn99 Mar 23, 2025

Choose a reason for hiding this comment

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

현재는 어떤 게 진짜 컴포넌트이고 어떤 게 스타일드 컴포넌트인지 코드만으로는 구분이 어려운 것 같습니다.

이런 문제를 해결하기 위해 스타일드 컴포넌트를 import * as S from './style'로 불러와 S.Wrapper처럼 구분지어 작성하는 방법이 가장 널리 사용되고 있는 걸로 알고 있습니다! 다른 많은 분들께서 유사한 방식으로 사용하고 계시니 살펴보셔도 좋을 것 같습니다!

저도 영서 님과 같은 방식으로 사용하고 있었는데, 이렇게 완전한 타인의 시선에서 코드를 보니 제 코드 스타일의 변화도 고려해 보게 되네요.

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

Choose a reason for hiding this comment

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

저는 보통 반복되는 UI나 관련 로직이 길어지는 경우에 컴포넌트를 분리하는 편입니다. 그래서 이 정도의 양이라면 HeaderTitle을 styles 폴더 아래에 두고 App.jsx 파일에 5줄의 코드를 작성했을 것 같은데요, 영서 님은 어떤 기준으로 컴포넌트를 분리하시는지 궁금합니다!

Comment on lines +17 to +23
{dailyTodos.map((todo) => {
if (todo.isFinished) {
finishedTodos.push(todo)
return
}
return <TodoItem key={todo.id} {...todo}></TodoItem>
})}
Copy link

Choose a reason for hiding this comment

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

이런 경우에는 dailyTodos를 순회하며 finishedTodos를 동적으로 만들기보다는 filter 메서드를 사용해서 개별 변수를 작성하는 게 가독성을 더 높일 수 있을 것 같습니다.

const unfinishedTodos = dailyTodos.filter((todo) => !todo.isFinished);
const finishedTodos = dailyTodos.filter((todo) => todo.isFinished);

이렇게 개별 변수를 선언하고 각 변수에 대해 map을 사용하면, 가독성도 높이고 기능을 추가하는 경우에도 유지보수 측면에서 유리할 것으로 생각됩니다!

Copy link
Author

Choose a reason for hiding this comment

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

미완료 todo를 상단에, 완료된 todo를 하단에 놓고 싶으면서도 isFinished 판별을 두 번 하고 싶지 않아서 이렇게 했는데 가독성 측면에서 미리 변수를 나누는 게 확실히 더 좋은 방법인 거 같아요! 반영하겠습니다!

Comment on lines +14 to +17
<HeadTitle>Weekly Check</HeadTitle>
<SubTitle $clickable={true} onClick={onClickDate}>
{`${dateObj.getFullYear()}${month}${week}째 주`}
</SubTitle>
Copy link

Choose a reason for hiding this comment

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

지금까지 App 컴포넌트에서 사용되는 스타일드 컴포넌트는 styles/ 폴더에 있고, 개별 컴포넌트에서 사용하는 스타일드 컴포넌트는 해당 컴포넌트 파일 내부에 선언되는 것으로 이해하고 있었는데요, WeeklyContentHeader에서만 사용되는 HeadTitle이나 SubTitle 같은 스타일드 컴포넌트도 styles/ 내부에 위치하고 있어 폴더 구조의 일관성이 다소 흐려지는 것 같습니다.

다른 컴포넌트들은 내부적으로 스타일드 컴포넌트를 선언하고 있기 때문에, HeadTitle이나 SubTitleWeeklyContentHeader 내부에 두는 것이 더 자연스러울 수 있을 것 같아요. 혹시 해당 스타일을 재사용할 계획이 있었던 건지, 또는 다른 특정한 이유로 styles/ 폴더에 위치한 것인지 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

HeadTitleSubTitleDailyContentHeaderWeeklyContentHeader 두 곳에서 사용하기 때문에 styles/ 폴더 하위에 분리했습니다.

Comment on lines +8 to +15
const StyledDiv = styled.div`
${ClickableStyle}
display: flex;
align-items: center;
gap: 10px;
padding: 8px 3px;
border-radius: 10px;
`
Copy link

Choose a reason for hiding this comment

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

투두 아이템에 ClickableStyle을 주고 있지만 실제로 클릭 이벤트에 대한 동작이 없어서 사용자에게 혼란을 줄 수 있을 것 같습니다. 전체 아이템에 cursor pointer를 주는 대신, MainContent를 label로 작성하고 cursor pointer를 주어서 텍스트 클릭 시에도 체크박스 change 이벤트가 동작하게 하면 이런 혼란을 줄일 수 있을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

입력해둔 todo가 많고 screen width가 넓은 상태일 경우를 위해 이렇게 디자인하는 편입니다. 사용자가 체크박스나 X 버튼에 마우스를 위치시켰을 때 어떤 아이템에 위치시켰는지 용이하게 보기 위해 Item 컴포넌트 전체에 hover 이벤트로 박스 쉐도우를 줬습니다.

이벤트는 체크박스와 X버튼에만 있지만 체크박스와 X버튼에만 작게 hover 이벤트를 주는 것보다 이렇게 하는 게 가시성이 훨씬 좋아서 항상 이렇게 디자인하는 편인 것 같습니다ㅎㅎ...

text를 복사할 경우를 대비하기 위해 text를 클릭하여 checkbox를 토글하는 방식은 제가 선호하는 방식은 아니긴 합니다. Clickable한 요소를 포함하고 있다는 의미이기도 한데 hover 이벤트에만 사용하니까 focusingStyle로 변수 이름을 바꾸면 혼란이 줄어들 수도 있을 것 같네요.

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.

2주차 과제도 수고하셨습니다!

- [리액트 투두 예시](https://reacttodoyyj0917.vercel.app/)
- [1주차 VanillaJS todo](https://vanilla-todo-21th-xi.vercel.app/)
- [2주차 React todo](https://react-todo-21th-smoky.vercel.app/)
- [figma](https://www.figma.com/design/oUJT679EyBJYQmCJgNdgvJ/CEOS-Frontend-21-%EA%B9%80%EC%98%81%EC%84%9C?node-id=0-1&p=f&t=xkmcJ7COhkHPcN02-0)

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.

감사합니다😇!!

import { createContext } from 'react'
import formatDate from './utils/formatDate'

import Header from './components/Header'

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. Container/>으로 사용하면 됩니다! 그러면 진짜 컴포넌트/스타일드 요소 간의 구분도 더 잘 돼서 가독성도 증가하고 개인적으로 훨씬 편하다고 느꼈어요!

Copy link
Author

Choose a reason for hiding this comment

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

저는 /styles, components로 폴더를 분리하는 정도까지만 생각했는데 서정님 코드를 보며 's.dot' 방법이 좋은 방식임을 느꼈습니다. 저도 적용해봐야겠어요!

Choose a reason for hiding this comment

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

코드를 보니 헤더, 푸터, 컨테이너 등의 스타일 요소는 app.jsx 한 파일에서 사용되는 것 같은데
그러면 이것들을 쪼개놓지 말고 하나의 파일로 작성하시는 것도 좋을 것 같아요! 과도한 코드스플릿과 모듈화는 지양하는 것이 좋을 것 같아요.

`

const TodoViewer = () => {
const { todos, pivotDate } = useContext(TodoStateContext)

Choose a reason for hiding this comment

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

저는 props 사용해서 무한 전달의 굴래에 빠졌었는데 useContext 사용하는거 좋은 아이디어인것 같아요!!

import ClickableStyle from '../styles/ClickableStyle'
import { TodoDispatchContext } from '../App'

const StyledDiv = styled.div`

Choose a reason for hiding this comment

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

component 파일과 styled 파일을 분리해놓으면 좋을 것 같습니다 ;)

Copy link
Author

Choose a reason for hiding this comment

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

저도 앞으로 styles 하위엔 전역에서 사용할 스타일이나 통일할 스타일을 넣고
components 하위에 컴포넌트별 폴더를 만든 후 해당 컴포넌트에서 사용할 스타일 파일과 기능 컴포넌트로 파일로 분리해야 할 것 같아요! 감사합니다.

}
}

export const TodoStateContext = createContext()

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.

좋은 것 같아요!!


return (
<div>
<SubTitle $clickable={true} onClick={onClickDate}>

Choose a reason for hiding this comment

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

전체 코드를 보니 항상 clickable은 true 값을 가지고 있는 것 같은데, 혹시 props로 넘겨주는 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

<HeadTitle/>의 경우 DailyContentHeader에서는 clickable이고 WeeklyContentHeader에서는 clickable하지 않아서 clickable을 확인하는 로직이 있는데요...
원래 Monthly View도 만들 생각이었고 Monthly View에서 사용할 <SubTitle/>이 클릭 이벤트를 가질지 결정하지 않아서 일단 <HeadTitle/>과 마찬가지로 두 컴포넌트 모두 clickable한지 검증하는 로직을 넣었습니다.
아마 저 혼자 Monthly View도 만들어볼 거 같은데 이때 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.

3 participants