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주차] 이지인 미션 제출합니다. #6

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

jinnyleeis
Copy link

@jinnyleeis jinnyleeis commented Mar 22, 2024

🥳 배포 링크

https://react-todo-19th-zeta.vercel.app/

⚙️ 구현한 기능

image 23
image 24

🤔 Key Questions

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

가상 돔은, 실제 DOM을 메모리에 복사해준 것입니다.
DOM의 내용이 바뀌면 이를 곧바로 새로운 DOM 트리를 생성하고, Render tree를 생성하는 것이 아닙니다.
내용이 바뀔 시, 우선 가상 돔에 렌더링합니다.
이후, 새로운 가상 DOM과 이전 가상 DOM을 비교하여 실제로 변경된 부분만 실제 DOM에 패치하는 과정을 거칩니다.

-> 실제 돔을 조작하는 것은 '레이아웃을 재계산'하는 과정과 변경된 레이아웃을 바탕으로 화면을 다시 그리는 '리플로우'와 '리페인트' 과정을 필요로 하므로 비용이 많이 드는 작업입니다. 때문에, 가상 DOM을 사용함으로써 필요한 최소한의 변경만 실제 DOM에 적용하여, 성능을 크게 향상시킬 수 있습니다.

-> 가상 DOM은 메모리 내에서 작동하기 때문에, DOM 조작보다 훨씬 빠르게 실행됩니다.

2. 미션을 진행하면서 느낀, React를 사용함으로서 얻을수 있는 장점은 무엇이었나요?

<명령적 UI 개발보다, 편리했던 선언적 UI 개발>
리엑트를 사용해 보면서, 컴포넌트 기반 구조를 사용하는 유니티 개발을 할 때와 유사하다고 느꼈습니다. 유니티는, 3D 씬에 오브젝트들을 배치하는 것이므로, 각각의 컴포넌트별로 개발하는 것이 적합한 구조입니다.
웹 페이지도, 결국, 2D 화면에 노드와 요소들을 배치하는 것이므로, 리엑트를 사용하면서 유니티 개발할 때와 유사하다는 생각이 들었습니다. 리엑트를 사용한 컴포넌트 방식이 코드를 작성하고, 계획을 세우는 데 더 편리하다고 느꼈습니다.
바닐라js로 개발했을 때처럼 document의 다양한 메서드를 사용하면서 복잡한 조작을 거치며 "어떻게" UI를 변경할 것인지에 대한 절차적 로직을 작성하는 대신,
컴포넌트별로 개발하며 "무엇"을 보여줄 것인지에 집중할 수 있어서 편리했던 것 같습니다.

3. React에서 상태란 무엇이고 어떻게 관리할 수 있을까요?

상태란?

리엑트에서 상태는 컴포넌트의 현재 상태에 대한 정보(컴포넌트의 현재 조건이나 데이터)를 보유하는 객체를 말합니다.
특정 시점에서 컴포넌트의 데이터의 스냅샷이라고 생각할 수 있을 것 같습니다.

상태를 관리하는 방법

리엑트를 통해 상태를 동적으로 관리하고, 업데이트 할 수 있습니다.
함수형 컴포넌트에선 상태를 선언하고 관리할 수 있게 해주는 내장 훅(Hook) 'useState'를 제공합니다.

  1. 섹션 컴포넌트 상태 관리
// 섹션 컴포넌트 일부분 
 const [todoText, setTodoText] = useState('');
  
    const handleAddTodo = () => {
// 공백일 시 추가x 
      if (todoText.trim() === '') return;
      addTodo(section.id, todoText);
      setTodoText('');
    };

Section 컴포넌트에서 useState를 사용하여 todoText 상태를 관리했습니다.
todoText 상태는, 해당 섹션의 할일로 입력된 것을 가지고 있는 텍스트입니다.
사용자가 특정 섹션에 할 일을 입력하고 추가 버튼을 클릭하면, handleAddTodo() 이벤트 핸들러 함수가 호출됩니다.

// 섹션 컴포넌트 리턴문 일부
 <Input
            type="text"
            placeholder="할 일 추가"
            value={todoText}
            onChange={(e) => setTodoText(e.target.value)}
          />
          <Button onClick={handleAddTodo}> 📥</Button>

이벤트 핸들러 내부에서 상위 컴포넌트(App 컴포넌트)로부터 전달받은 addTodo() 함수를 호출합니다.
섹션 컴포넌트의 sectionId와 todoText 상태를 addTodo()의 인자로 전달해 새로운 할 일 객체를 생성하고 섹션의 할 일 목록에 추가합니다. 이에 따라 UI가 자동으로 업데이트됩니다.

   	const addTodo = (sectionId, todoText) => {
   	setSections((prevSections) =>
   		prevSections.map((section) =>
   			section.id === sectionId
   				? {
   						...section,
   						todos: [
   							...section.todos,
   							// 해당 섹선에 새로운 할 일 객체 생성하는 부분
   							{ id: Date.now(), text: todoText, completed: false },
   						],
   				  }
   				: section
   		)
   	);
   };

아래는 sections를 상태로 가지는 섹션의 상위 컴포넌트인 앱 컴포넌트의 코드 일부입니다.

function App() {
	const [sections, setSections] = useState([]);
	const [sectionName, setSectionName] = useState('');
  1. 투두 아이템 컴포넌트에서 상태 관리
    TodoItem 컴포넌트에서는 섹션과 달리 상태 관리를 직접 하지 않았습니다.
    상위 컴포넌트로부터 할 일 객체(todo), 삭제(onDelete), 완료 상태 토글(onToggleCompleted) 함수를 props로 받았습니다.
// 섹션 컴포넌트 return 일부 ...
 {section.todos.map((todo) => (
          <TodoItem
            key={todo.id}
            todo={todo}
            onDelete={() => deleteTodo(section.id, todo.id)}
            onToggleCompleted={() => onToggleCompleted(section.id, todo.id)}
          />
          
        ))}

상위 컴포넌트로부터 받은 상태 정보에 따라 투두 아이템을 어떻게 렌더링할지 결정했습니다.

// todoitem 컴포넌트 코드 일부...
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
  {todo.text}{todo.completed ? ' 🥳' : ''}
</span>
<Button onClick={() => onToggleCompleted(todo.id)}>
  {todo.completed ? '❎' : '✅'}
</Button>
<Button onClick={() => onDelete(todo.id)}>🗑️</Button>
  • 할 일이 완료된 경우
    -> 해당 할 일의 텍스트에 취소선을 적용 + ✅ 버튼을 렌더링 + 🥳 텍스트 표시
  • 할 일이 완료되지 않은 경우
  • 해당 할 일의 텍스트에 취소선 적용x + ❎ 버튼을 렌더링 + 🥳 텍스트 표시 x

  • 할 일을 삭제한 경우
    -> 상위 컴포넌트 섹션으로부터 제공받은 onDelete(todo.id) 이벤트 핸들링 함수를 통해
    할 일 삭제

4. Styled-Components 사용 후기 (CSS와 비교)

Styled-Components를 통해 CSS를 JS 파일 내에 직접 작성하고, 스타일의 범위를 컴포넌트로 한정지어 사용할 수 있어서 개발하는데 더 직관적이였던 것 같습니다.
스타일드 컴포넌트를 사용하여 리액트의 JSX 문법을 작성할 때 간편하게 컴포넌트의 스타일을 동적으로 변경할 수 있어서, 코드 작성하기 직관적이고 아주 편리했던 것 같습니다.

// 완료 여부에 따른 동적 스타일 변경 
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>

💡 여담

1주차 과제를 할 때 html 태그에 적용한 css 속성이 특정 요소에는 적용되고 특정 요소에는 적용되지 않아서,
임시방편으로 css 속성이 적용이 필요한 요소마다 css 속성을 정의하는 방법으로 해결하였습니다.
그런데 지난 세션에서 유담님과 이야기하면서,
'*' 선택자를 사용하면, 하나씩 요소별로 속성을 정의하지 않아도, 모두 적용된다는걸 알아, *를 사용하는 간결한 코드로 변경하였습니다.
이에 관해 이야기하는 과정에서 브라우저에는 기본적으로 내장된 CSS 스타일이 존재하며, 이는 웹 페이지가 아무런 사용자 정의 스타일 없이도 기본적인 형태를 갖출 수 있도록 하여, 사용자가 정의한 CSS와 브라우저의 기본 스타일 사이에 충돌이 발생할 수 있다는 걸 알았습니다.
브라우저의 기본 스타일과 사용자 정의 스타일 간의 충돌을 해결하기 위해 CSS 선택자의 우선순위를 이해하고 적절히 사용하거나 CSS 리셋(reset)이나 노멀라이즈(normalize)와 같은 기법을 사용하여 브라우저 간의 스타일 차이를 최소화하고, 일관된 스타일링을 적용하는 것을 고려해야겠다고 생각했습니다.

src/App.js Outdated
flex-direction: column;
align-items: center;
width: 80%;
margin: 30px auto;
Copy link

Choose a reason for hiding this comment

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

px단위도 좋지만 반응형을 위해 rem단위를 사용했어도 좋았을 거 같아요!

src/App.js Outdated
@@ -137,17 +136,17 @@ function App() {
return (
<AppContainer>
<GlobalStyle />
<SummaryText>{today} </SummaryText>
<SummaryText>{today}</SummaryText>
Copy link

Choose a reason for hiding this comment

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

간단한 이미지는 img, svg말고 이모지를 사용하는 거 좋은 거 같아요!

Comment on lines +4 to +9
@font-face {
font-family: 'SUIT-Regular';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/[email protected]/SUIT-Regular.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
Copy link

Choose a reason for hiding this comment

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

폰트 적용 좋아요!

src/Section.js Outdated
Comment on lines 73 to 74
<div>
<Input
type="text"
placeholder="할 일 추가"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<Button onClick={handleAddTodo}>추가</Button>
</div>
Copy link

Choose a reason for hiding this comment

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

삭제하는 과정에서 감싸고 있던 빈<div>태그가 살아있어요!

Comment on lines +84 to +92
<div>
<Input
type="text"
placeholder="할 일 추가"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<Button onClick={handleAddTodo}> 📥</Button>
</div>
Copy link

Choose a reason for hiding this comment

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

<div>에 flex등 스타일을 적용시켜주면 모든 할일section에서 일관성 있게 input창, button이 나올 수 있을 거 같아요!

Comment on lines +34 to +39
const ButtonContainer = styled.div`
display: flex;
justify-content: flex-end; // 버튼들을 오른쪽 끝으로 정렬
align-items: stretch;
margin-bottom: 10px;
`;
Copy link

@songess songess Mar 23, 2024

Choose a reason for hiding this comment

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

이미 부모 태그인 <ItemContainer>에서 justify-content: space-between;가 적용되어 있어 오른쪽 끝에 배치되어 있고 여기서는 width도 따로 적용되어 있지 않아 justify-content: flex-end;가 필요하지 않을 거 같아요!
margin-bottom: 10px;도 좋지만 가운데 배치를 위해선 margin: 5px 0;를 주거나 배치가 조금 헝크러진다면 부모한테 padding을 줘도 될 것 같아요!

src/App.js Outdated
Comment on lines 122 to 128
const [remainingTodos, completedTodos] = sections.reduce(
([remaining, completed], section) => [
remaining + section.todos.filter((todo) => !todo.completed).length,
completed + section.todos.filter((todo) => todo.completed).length,
],
[0, 0]
);
Copy link

@songess songess Mar 23, 2024

Choose a reason for hiding this comment

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

고차함수 reduce를 사용해 할일 갯수 계산을 한번에 처리한게 멋져요!

Comment on lines +104 to +119
const toggleTodoCompleted = (sectionId, todoId) => {
setSections((prevSections) =>
prevSections.map((section) =>
section.id === sectionId
? {
...section,
todos: section.todos.map((todo) =>
todo.id === todoId
? { ...todo, completed: !todo.completed }
: todo
),
}
: section
)
);
};
Copy link

Choose a reason for hiding this comment

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

객체배열 안에있는 객체배열의 상태를 다뤄야 해서 함수를 짜는 데 어려움이 있었을 거 같은데 map고차함수와 삼항연산자를 통해 해결하신게 멋져요!

Choose a reason for hiding this comment

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

고차함수랑 삼항연산자로 잘 표현해주셨는데 저는 아직 부족해서그런지 삼항연산자를 풀어도 좋을 것 같긴합니다!!!

src/App.js Outdated
Comment on lines 45 to 47
useEffect(() => {
localStorage.setItem('sections', JSON.stringify(sections));
}, [sections]);
Copy link

Choose a reason for hiding this comment

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

useEffect를 사용해 sections상태가 업데이트 될 때마다 알아서 localStorage가 업데이트 돼서 좋은 거 같아요!

Comment on lines +45 to +50
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>
<span>{todo.completed ? '🥳' : ''}</span>
Copy link

Choose a reason for hiding this comment

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

부모태그 <ItemContainer>justify-content: space-between;가 적용되어 있어 {todo.text}의 길이에 따라 이모지가 뜨는 위치가 변경되는데 버튼 옆에 뜨게 한다거나, 정 가운데 뜨게 한다거나 일관된 위치에 이모지가 나왔어도 좋을 거 같아요!

Choose a reason for hiding this comment

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

이모지 너무 귀여워요

@songess
Copy link

songess commented Mar 23, 2024

과제하느라 고생하셨어요!

UI도 이쁘고, 고차함수도 사용해주셔서 멋졌어요!
할일의 항목을 구분해 따로 볼 수 있어서 좋았습니다.

다음 과제도 화이팅이에요!

Copy link

@Dahn12 Dahn12 left a comment

Choose a reason for hiding this comment

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

지인님 이번 미션도 정말 수고 많으셨습니다! pr에 로컬 스토리지 구조 설명해 주신게 정말 인상깊었어요! 다음주 미션도 같이 화이팅 해봐요👍

Comment on lines +74 to +89
const addTodo = (sectionId, todoText) => {
setSections((prevSections) =>
prevSections.map((section) =>
section.id === sectionId
? {
...section,
todos: [
...section.todos,
// 해당 섹선에 새로운 할 일 객체 생성하는 부분
{ id: Date.now(), text: todoText, completed: false },
],
}
: section
)
);
};
Copy link

Choose a reason for hiding this comment

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

이 부분 pr에 적어주신 local storage부분을 보면서 중첩 배열을 적용하셨다는게 정말 도움이 많이 되었습니다...!👍

const [todoText, setTodoText] = useState('');

const handleAddTodo = () => {
if (todoText.trim() === '') return;
Copy link

Choose a reason for hiding this comment

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

이부분에 alert를 주셔도 좋을거 같아요!

Comment on lines +85 to +90
<Input
type="text"
placeholder="할 일 추가"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
Copy link

Choose a reason for hiding this comment

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

이부분을 form을 사용하면 엔터키를 따로 적용해 주지 않아도 submit 된답니다! 제 1주차 미션 코드리뷰 보시면 많은 분들이 이부분에 대해 도움을 주셨어요! 한번 확인해 보시는 것도 추천드립니다~

Comment on lines +17 to +32
const Button = styled.button`
padding: 0px 7px;
border-radius: 5px;
border: none;
background-color: #f7f7f7;
color: black;
font-size: 17px;
cursor: pointer;
margin-left: 2px;
margin-right: 4px;
width: 100%;

&:hover {
background-color: #0056b3;
}
`;
Copy link

Choose a reason for hiding this comment

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

자주 쓰는 css의 경우에는 styled component에서 props로 쓸 수도 있더라구요! 저는 이번 미션에서 이블로그를 참고했는데요, 이외에도 styled component의 다양한 활용방법이 있는것 같으니 스터디에서 더 이야기해봐요~!

Copy link

Choose a reason for hiding this comment

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

리액트는 통용화되는 폴더 구조가 있더라구요...! 리액트 폴더구조 확인해 보시면 좋을거 같아요~

Copy link

@westofsky westofsky left a comment

Choose a reason for hiding this comment

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

안녕하세요~~ 프론트 운영진 배성준입니다!!

리액트 처음 공부하시는 것 같은데 useState,useEffect과 같은 훅도 자유롭게 사용하셔서
괜히 뿌듯해지면서 코드리뷰했습니다!!
다른 분들이 말해주신 것처럼 폴더구조나 컴포넌트를 더 나누어서 가독성이나 렌더링 최적화를 챙겨도 좋을 것 같아요 ㅎㅎ
다음 과제도 기대하겠습니다!!

Comment on lines +1 to +6
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import Section from './Section';
import GlobalStyle from './GlobalStyle';

const AppContainer = styled.div`

Choose a reason for hiding this comment

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

혹시 styled-component를 메인 함수 상단에 선언하시는 기준이 있나요 ?? 저는 컴포넌트가 어떠한 기능을 담고 어떠한 구조를 갖고 있는지 먼저 볼라고 스타일 속성은 최하단에 두는 편인데 지인님의 기준이 궁금합니다!

type='text'
placeholder='새 섹션 이름'
value={sectionName}
onChange={(e) => setSectionName(e.target.value)}

Choose a reason for hiding this comment

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

최상단 component인 App.js에서 onChange를 통해 입력할때마다 setState하면 전체가 계속 재렌더링되는 것 같아서 컴포넌트를 분리하거나 uncontrolled component로 관리해도 좋을 것 같습니다 !! 참고 해주시면 좋을 것 같아요

Comment on lines +74 to +89
const addTodo = (sectionId, todoText) => {
setSections((prevSections) =>
prevSections.map((section) =>
section.id === sectionId
? {
...section,
todos: [
...section.todos,
// 해당 섹선에 새로운 할 일 객체 생성하는 부분
{ id: Date.now(), text: todoText, completed: false },
],
}
: section
)
);
};

Choose a reason for hiding this comment

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

Add하는 로직에서 엔터키 입력도 받아서 처리해주면 사용자경험 측면에서 좋을 것 같아요!

Comment on lines +104 to +119
const toggleTodoCompleted = (sectionId, todoId) => {
setSections((prevSections) =>
prevSections.map((section) =>
section.id === sectionId
? {
...section,
todos: section.todos.map((todo) =>
todo.id === todoId
? { ...todo, completed: !todo.completed }
: todo
),
}
: section
)
);
};

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 +50
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>
<span>{todo.completed ? '🥳' : ''}</span>

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 +49
<span
style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
>
{todo.text}
</span>

Choose a reason for hiding this comment

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

image

처럼 길어지면 스타일이 조금 깨지는 것 같아서 width를 정해줘도 좋을 것 같습니다!

Comment on lines +122 to +128
const [remainingTodos, completedTodos] = sections.reduce(
([remaining, completed], section) => [
remaining + section.todos.filter((todo) => !todo.completed).length,
completed + section.todos.filter((todo) => todo.completed).length,
],
[0, 0]
);

Choose a reason for hiding this comment

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

image

남은 것, 완료한 게 같은 아이콘이라서 헷갈리는 것 같아요...!!

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