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

[2주차] 심여은 미션 제출합니다. #5

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

ongheong
Copy link
Member

@ongheong ongheong commented Sep 3, 2024

미션 소감

안녕하세요, LG U+유레카 1기 프론트엔드 반 심여은입니다.
이번에는 저번 주차에 바닐라 js로 만들었던 todo 리스트를 리액트로 변환하는 작업을 해봤습니다. 직접 DOM 엘리먼트를 가져올 필요가 없어서 코드가 더 단순화되었고, 부모 컴포넌트인 App에서 자식 컴포넌트들에게 업데이트된 state를 넘기는 방식으로 진행하여 새로운 todoList를 렌더링하는 방식이 더 간편해졌습니다! 또한 css-in-js인 emotion 라이브러리로 스타일을 작성하니 컴포넌트 이름으로 스타일을 적용하여 클래스 이름이 중복될 일이 없어 좋았어요!
하지만 아직 컴포넌트의 랜더링 시점, useEffect의 실행 시점 등 상태에 대해서는 익숙하지 않은 부분이 많은 것 같네요ㅠㅠ 작성한 코드에 미숙한 부분이 많을텐데, 리뷰 마구 날려주시면 정말 감사하겠습니다!
+이전 js 코드에서 구현했던 기능이 현재 제대로 작동하지 않는 경우가 있습니다. 추후 수정해서 올리겠습니다! => 수정했습니다!
++ react-todo 배포했습니다! https://react-todo-ongheong.vercel.app/

image

미션에서 특별히 신경 쓴 부분들

  • 기능별로 컴포넌트 분리: 기존 html태그에서 기능을 중점으로 나누어 Header, InputForm, TodoList, TodoItem으로 컴포넌트를 분리해봤습니다.
  • 조건부 렌더링: localStorage에서 가져온 todoList를 isComplete boolean 변수에 따라 조건부로 렌더링되도록 작성했습니다.
  • 컴포넌트 네이밍: 스타일 컴포넌트와 공통 컴포넌트를 분리하기 위해 스타일 컴포넌트의 접미사로 "Style"을 붙였습니다.

KEY QUESTION

1. 지난주 미션과 비교했을 때, React로 구현하는 과정에서 어떤 점이 더 편리하거나 복잡했나요?

직접 DOM 엘리먼트를 조작하지 않아 const 변수들을 추가할 필요가 없어져 코드가 단순해졌습니다. 또한 부모 컴포넌트인 App에서 state, setState를 정의하고, 자식 컴포넌트들에게 업데이트된 state를 넘기는 방식으로 진행하여 새로운 todoList를 렌더링하는 방식이 더 간편해졌습니다!
하지만 원하는 리랜더링을 위해서 해당 컴포넌트가 가진 state를 변경하는 부분에 더욱 주의하게 되었습니다. 또한 props로 값을 전달할 때 자식에서 부모로 전달하는 방식을 생각하는 것이 쉽지 않았습니다🥲

2. React의 Virtual DOM(가상 돔)이란 무엇이고, 이를 사용했을 때의 장점은 무엇이 있나요?

  • Virtual DOM은 웹페이지와 실제 DOM 사이에서 중간 매개체 역할을 합니다.
  • 기본적으로 사용자와 상호작용을 하는 웹페이지는 DOM을 수정하여 수시로 화면의 업데이트를 합니다.
  • 수정할 부분을 DOM의 데이터에서 다 찾아야 하는 기존 방식과 달리, Virtual DOM에서는 state가 바뀐 컴포넌트만 업데이트하고 리랜더링합니다.

3. React에서 state와 props는 무엇이고 어떻게 사용하나요?

  • State는 리액트 컴포넌트의 변경 가능한 데이터를 뜻합니다.
  • state가 변경되면 컴포넌트가 리랜더링되므로, 렌더링이나 데이터 흐름에 사용되는 값만 state에 포함시켜야 합니다.
  • 클래스 컴포넌트의 경우 state를 생성자에서 정의하고, 함수 컴포넌트의 경우 useState() 훅을 사용하여 정의합니다.
  • state는 객체이므로 직접 값을 변경하는 것이 아닌, setState() 함수를 통해 수정해야 합니다.
  • Props는 컴포넌트에 전달할 정보를 담고 있는 JS 객체입니다.
  • 모든 리액트 컴포넌트는 그들의 props에 대해 순수 함수의 역할을 해야 합니다. 즉, props는 바꿀 수 없고, 같은 props가 들어오면 항상 같은 엘리먼트를 반환해야 합니다.
 function App(props) {
   return (
   	<Profile
   	    name= "옹헤"
   	    introduction= "안녕하세요"
   	    viewCount={1500}
   	/>
   );
}

4. React에서 컴포넌트를 분리하는 기준은 무엇일까요?

이 부분에 대해서는 사람마다 정의가 조금씩 다를 것이라고 생각합니다! 다음의 나열된 기준들 중에서 저는 재사용성과 단일 책임 원칙에 주의하며 분리하는 편입니다.

  1. 재사용성 (Reusability)
  • 다른 부분에서도 사용될 수 있는지 여부
  • 재사용 가능한 컴포넌트 → 효율성 증가
  • 코드 중복 감소
  1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
  • 각 컴포넌트는 하나의 기능만 수행
  • 컴포넌트 이해하기 쉬워짐
  • 유지보수 간단
  1. 가독성 (Readability)
  • 너무 많은 JSX, 로직 포함 → 더 작은 단위로 분리
  1. 상태와 라이프사이클 (State and Lifecycle)
  • 컴포넌트의 성능과 동작에 미치는 영향을 고려하여 분리
  1. UI 요소 (UI Elements)
  • 서로 다른 시각적 요소는 컴포넌트로 분리
  • 디자인 변경, 유지보수 시 유연성 제공

Copy link

@jaee55555 jaee55555 left a comment

Choose a reason for hiding this comment

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

여은님, 일주일간 고생하셨습니다!
여은님의 배포 페이지를 보면서 귀여운 디자인에 빠졌던 것같습니다😍
그리고 변수명과 컴포넌트 구조가 이해하기 쉬워서 빨리 이해할 수 있었어요!
emotion의 사용법을 여은님의 코드를 보면서 더 많이 배웠습니다🤩
한 주동안 고생하셨습니다~!

function App() {
return <div>React Todo</div>;
const userName = '옹헤';

Choose a reason for hiding this comment

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

옹헤 이름이 너무 귀여워요...😻

Comment on lines +9 to +49
const GlobalStyles = () => (
<Global
styles={css`
@font-face {
font-family: 'RixXladywatermelonR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/RixXladywatermelonR.woff2')
format('woff2');
font-weight: normal;
font-style: normal;
}

html {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

body {
font-family: 'RixXladywatermelonR', sans-serif;
max-width: 500px;
padding: 10px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
scrollbar-width: thin;
margin: 0;
}

button {
font-family: 'RixXladywatermelonR';
display: flex;
justify-content: center;
align-content: center;
flex-wrap: wrap-reverse;
}
`}
/>
);

Choose a reason for hiding this comment

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

Global style을 통해서 하나의 디자인 컴포넌트에 한번에 css를 정리할 수 있군요!🤩

Copy link
Member

Choose a reason for hiding this comment

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

주현님에게 전역 스타일에 대해 리뷰를 들었는데 이렇게 사용하는 거군요! 확실히 훨씬 깔끔한 것 같아요 다음 미션 때 참고해서 적용해보겠습니다😆

Comment on lines +54 to +58
//입력값이 없으면 alert 띄우기
if (todoInput === '') {
alert('한 글자 이상 입력해주세요.');
return;
}

Choose a reason for hiding this comment

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

빈 문자열 방지하는 alert 좋은 것같아요~!👍

Comment on lines +26 to +30
export default function TodoList({ todoList, handleComplete, handleDelete }) {
// todoList에서 완료되지 않은 todoList와 완료된 todoList를 분리
const doList = todoList.filter((todo) => !todo.isCompleted);
const doneList = todoList.filter((todo) => todo.isCompleted);

Choose a reason for hiding this comment

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

filter 사용해서 doList와 doneList에 나눠서 처리해주시는거 좋은 것같아요!🤩

Comment on lines +81 to +85

//todoList가 변경될 때마다 localStorage에 저장
useEffect(() => {
localStorage.setItem('todoList', JSON.stringify(todoList));
}, [todoList]);

Choose a reason for hiding this comment

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

useEffect 사용해서 재랜더링 조건 부여하신거 좋은 것 같습니다!

Comment on lines +89 to +90
const newTodo = {
createTime: Date.now(),
Copy link

@jaee55555 jaee55555 Sep 5, 2024

Choose a reason for hiding this comment

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

🙋질문 있습니다~!
혹시 id(key)의 변수명을 createTime으로 명명하신 이유가 있을 까요?
지금 위치에서 변수명의 뜻을 바로 이해할 수 있지만,
해당 props를 받아서 사용하는 다른 컴포넌트에서는 id값이라고 명확하게 이해하기가 쉽지 않을 것 같아서요..!

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

@jejukyj jejukyj 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 +9 to +49
const GlobalStyles = () => (
<Global
styles={css`
@font-face {
font-family: 'RixXladywatermelonR';
src: url('https://fastly.jsdelivr.net/gh/projectnoonnu/[email protected]/RixXladywatermelonR.woff2')
format('woff2');
font-weight: normal;
font-style: normal;
}

html {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}

body {
font-family: 'RixXladywatermelonR', sans-serif;
max-width: 500px;
padding: 10px;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
scrollbar-width: thin;
margin: 0;
}

button {
font-family: 'RixXladywatermelonR';
display: flex;
justify-content: center;
align-content: center;
flex-wrap: wrap-reverse;
}
`}
/>
);
Copy link
Member

Choose a reason for hiding this comment

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

주현님에게 전역 스타일에 대해 리뷰를 들었는데 이렇게 사용하는 거군요! 확실히 훨씬 깔끔한 것 같아요 다음 미션 때 참고해서 적용해보겠습니다😆

Comment on lines +89 to +90
const newTodo = {
createTime: Date.now(),
Copy link
Member

Choose a reason for hiding this comment

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

저도 같은 이유로 궁금합니다..!

content: todoInput,
isCompleted: false,
};
setTodoList([...todoList, newTodo]);
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.

spread 연산자 사용 아주 좋습니다! 다만 setState를 사용할 때 변경되는 state를 직접 참조하여 사용하는 것보다는 콜백 함수를 작성하는 방식이 조금 더 안정적인 방식입니다 🙂

Suggested change
setTodoList([...todoList, newTodo]);
setTodoList((prev) => [...prev, newTodo]);

그 이유가 궁금하다면...

프로젝트를 진행하다 보면 state 변경 -> 렌더링 -> state 변경 -> 렌더링 -> ... 과정을 반복하게 됩니다.

setState(state + 1)은 현재의 state 값을 기반으로 상태를 업데이트 합니다. 하지만 이 방식은 state 값이 변경되는 과정에서 다른 setState 호출이 있을 경우, 이전 렌더링 시점의 상태를 기반으로 하기 때문에 최신 상태값을 반영하지 못할 수 있습니다.

반면 setState(prev => prev + 1)은 콜백 함수를 사용하여 이전 상태(prev)를 기준으로 상태를 업데이트 합니다. 이 방법은 상태 업데이트가 비동기적으로 처리되는 동안에도 항상 최신 상태를 반영하므로, 상태가 여러 번 업데이트 될 때에도 정확한 결과를 보장한다고 합니다!

Comment on lines +44 to +56
<ListLiStyle>
{todo.isCompleted ? (
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle>
) : (
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle>
)}
{todo.isCompleted ? (
<DoneSpanStyle>{todo.content}</DoneSpanStyle>
) : (
<span>{todo.content}</span>
)}
<DeleteBtnStyle onClick={() => handleDelete(todo)}>x</DeleteBtnStyle>
</ListLiStyle>
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

@corinthionia corinthionia left a comment

Choose a reason for hiding this comment

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

안녕하세요 여은 님!
메모장 UI가 볼수록 귀엽네요 ㅎㅎ 네이밍 컨벤션도 잘 지켜 주시고, 코드도 간결하게 잘 작성해 주셔서 리뷰하기 굉장히 편했습니다!
이번주 미션도 고생하셨습니다 🙌

Comment on lines +9 to +11
const GlobalStyles = () => (
<Global
styles={css`
Copy link
Member

Choose a reason for hiding this comment

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

오호 Global 컴포넌트로 전역 스타일 설정하신 점 아주 좋습니다!!
이 부분을 별도의 컴포넌트로 분리하고 import해서 사용하면 더욱 깔끔할 것 같아요 👍🏻

Comment on lines 74 to +75
function App() {
return <div>React Todo</div>;
const userName = '옹헤';
Copy link
Member

Choose a reason for hiding this comment

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

상수화 하여 사용하신 점 아주 좋습니다!!
네이밍 컨벤션 측면이긴 하지만 상수값을 사용할 때에는 UPPER_SNAKE_CASE를 사용한답니다! (참고)

Copy link
Member

Choose a reason for hiding this comment

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

또한 컴포넌트는 함수이기 때문에 상태가 업데이트 될 때마다 컴포넌트 내부에 선언된 변수는 매번 메모리에 재할당 됩니다!
하지만 상수는 재할당 될 필요가 없기 때문에 주로 컴포넌트 밖에서 선언해 줍니다 🙂

Suggested change
function App() {
return <div>React Todo</div>;
const userName = '옹헤';
const USER_NAME = '옹헤';
function App() {
// 생략
}

참고자료

content: todoInput,
isCompleted: false,
};
setTodoList([...todoList, newTodo]);
Copy link
Member

Choose a reason for hiding this comment

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

spread 연산자 사용 아주 좋습니다! 다만 setState를 사용할 때 변경되는 state를 직접 참조하여 사용하는 것보다는 콜백 함수를 작성하는 방식이 조금 더 안정적인 방식입니다 🙂

Suggested change
setTodoList([...todoList, newTodo]);
setTodoList((prev) => [...prev, newTodo]);

그 이유가 궁금하다면...

프로젝트를 진행하다 보면 state 변경 -> 렌더링 -> state 변경 -> 렌더링 -> ... 과정을 반복하게 됩니다.

setState(state + 1)은 현재의 state 값을 기반으로 상태를 업데이트 합니다. 하지만 이 방식은 state 값이 변경되는 과정에서 다른 setState 호출이 있을 경우, 이전 렌더링 시점의 상태를 기반으로 하기 때문에 최신 상태값을 반영하지 못할 수 있습니다.

반면 setState(prev => prev + 1)은 콜백 함수를 사용하여 이전 상태(prev)를 기준으로 상태를 업데이트 합니다. 이 방법은 상태 업데이트가 비동기적으로 처리되는 동안에도 항상 최신 상태를 반영하므로, 상태가 여러 번 업데이트 될 때에도 정확한 결과를 보장한다고 합니다!

Comment on lines +108 to +110
const newTodoList = todoList.filter((item) => {
return item.createTime !== todo.createTime;
});
Copy link
Member

Choose a reason for hiding this comment

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

함수가 직접 표현식을 반환하는 경우에는 중괄호와 return 키워드를 생략하여 사용할 수 있습니다!

Suggested change
const newTodoList = todoList.filter((item) => {
return item.createTime !== todo.createTime;
});
const newTodoList = todoList.filter(
item => item.createTime !== todo.createTime
);

import styled from '@emotion/styled';
import { useState } from 'react';

const InputFormStyle = styled.form`
Copy link
Member

Choose a reason for hiding this comment

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

InputForm 컴포넌트가 메인이다 보니 스타일 관련 코드는 InputForm 아래에 위치시키는 게 좋을 것 같아요!

export default function InputForm({ handleAddTodo }) {
const [todoInput, setTodoInput] = useState('');

const handleInputChange = (e) => {
Copy link
Member

Choose a reason for hiding this comment

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

네이밍 아주 좋습니다 👍🏻

Comment on lines +45 to +54
{todo.isCompleted ? (
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle>
) : (
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle>
)}
{todo.isCompleted ? (
<DoneSpanStyle>{todo.content}</DoneSpanStyle>
) : (
<span>{todo.content}</span>
)}
Copy link
Member

Choose a reason for hiding this comment

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

todo.isCompleted 값에 따라 렌더링 하는 부분을 2개로 나누어 작성하신 이유가 있으신가요?!
아래처럼 작성하시면 더욱 깔끔할 것 같아요! 👍🏻

Suggested change
{todo.isCompleted ? (
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle>
) : (
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle>
)}
{todo.isCompleted ? (
<DoneSpanStyle>{todo.content}</DoneSpanStyle>
) : (
<span>{todo.content}</span>
)}
{todo.isCompleted ? (
<>
<DoneBtnStyle onClick={() => handleComplete(todo)}>v</DoneBtnStyle>{' '}
<DoneSpanStyle>{todo.content}</DoneSpanStyle>
</>
) : (
<>
<DoBtnStyle onClick={() => handleComplete(todo)}></DoBtnStyle>
<span>{todo.content}</span>
</>
)}

Comment on lines +34 to +46
<SectionTitleStyle>📋 Todo ({doList.length})</SectionTitleStyle>
<ListUlStyle>
{doList.map((todo) =>
<TodoItem
key={todo.createTime}
todo={todo}
handleComplete={handleComplete}
handleDelete={handleDelete}
/>
)}
</ListUlStyle>
</SectionStyle>
<SectionStyle>
Copy link
Member

Choose a reason for hiding this comment

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

요 부분을 컴포넌트로 한 번 더 분리해도 좋을 것 같아요!

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.

4 participants