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 15 commits into
base: master
Choose a base branch
from

Conversation

songess
Copy link

@songess songess commented Mar 22, 2024

2주차 미션: React-Todo

배포

todo

기능구현

  • todo 추가 및 삭제
  • 유효성 체크, 중복 체크
  • todo 완료 여부에 따른 이동
  • 로컬스토리지에 저장
  • 날짜 표시
  • 갯수체크
  • 반응형 구현
  • enter시 추가, overflow시 scroll

리뷰

리액트의 hooks, 그리고 상태를 사용해 변경되면 자동으로 리렌더링을 해주는 방식 덕분에 JS만을 사용했을때보다 직접 신경써줘야 할 부분이 적었습니다.

선연형 프로그래밍 패러다임을 지키기 위해 각 기능별로 컴포넌트화 시키다보니 각 컴포넌트에서 todoList들을 따로 접근해야 했고, 하나의 컴포넌트에서 변경이 이루어지면 다른 컴포넌트들도 변경이 일어난 걸 알아야 하니 이를 어떻게 처리할 지 고민이 되었습니다.
일반적으로 props 전달방식, 혹은 전역상태관리로 해결하겠지만 이번에는 Localstorage를 사용하기 떄문에 이것만으로 해결할 수 있지 않을까 싶었습니다. 하지만 직접해보니 localstorage는 리액트의 상태가 아니기 떄문에 변경이 되어도 리렌더링이 되지 않았고 새로고침을 해야 정상적으로 변경사항이 반영됐습니다.
localstorage에 있는 값이 변경될때마다 모든 컴포넌트에서 localstorage의 값을 다시 받아오는 과정은 굉장히 비효율적인 거 같아 결국 처음에 생각했던 방법을 사용하기로 했습니다. 프로젝트가 크지 않기 때문에 전역상태관리 없이 props drilling으로도 해결이 가능할 거 같아 props로 전달하는 방식을 선택했습니다. 구현해보니 생각보다 drilling이 많이 생겨 전역상태관리로 해결했어도 좋았겠다 싶었습니다.

Key Questions

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

리액트에서 사용하는 가상 돔으로, 리액트 변경점이 발생하면 가상 돔이 변경되고, 실제 DOM과 차이점을 확인 후 해당하는 부분만 변경이 이루어집니다. 이를 통해 DOM의 변경을 최소화 시킬 수 있습니다.

리액트는 마운트가 된 이후 변경사항이 발생하면 리렌더링이 발생합니다. js에서 속성변경 등으로 이루어지는 리렌더링 역시 수정되는 부분만 수정되지만, 이는 개발자가 직접 컨트롤 해줘야 합니다. 하지만 react는 선언적 UI를 사용하기 때문에 상태가 변하면 자동으로 UI를 필요한 부분만 업데이트 해줍니다.

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

html 태그들 대신 컴포넌트들을 볼 수 있기 때문에 해당 코드(컴포넌트)가 무엇을 하려고 하는 지 알기 쉬웠습니다. 코딩하는 과정에서도 어느 부분을 내가 짜고 있는지, 어디서 에러가 났는지 찾지 편했습니다.

상태라는 개념을 통해 변경사항이 자동으로 변경되고, hooks들을 통해 이미 만들어진 함수들을 사용해 개발자의 부담을 덜어줬습니다.

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

컴포넌트의 현재 상태를 의미합니다. useState를 사용해 상태변수를 생성하고, 관리할 수 있습니다.

리액트는 변수를 관리하지 않습니다. useStatehook을 사용해 상태를 선언하면 이를 관리하며 변경사항이 생기면 이를 반영해 리렌더링을 시킵니다.

주의할 점은 useState는 비동기처리 된다는 것입니다. set함수를 호출하면 이는 스케쥴링되는데 스케쥴링 되어있는 상태에서 또 set함수를 호출하면 변경이 이루어지지 않은 값을 사용합니다. 따라서 리렌더링 되지 않았을 때 값에 접근하는 것을 주의해야 하고, 그럼에도 set함수를 호출해야 한다면

set함수((prev)=>{...})

방식을 활용해 이전 값에 올바르게 접근할 수 있게 해줘야합니다.

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

CSS를 JS파일 안에서 컨트롤 할 수 있기 때문에 수정하고 싶을때 CSS-IN-CSS 방식을 사용할 때보다 빠르게 찾아 수정할 수 있었습니다. 또한 props를 통해 동적인 스타일링을 할 수 있는 것도 장점입니다.

다만 스타일드 컴포넌트와 일반적인 컴포넌트를 구분할 수 없어서 이 부분이 불편했습니다.

Copy link

Choose a reason for hiding this comment

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

React에서 GlobalStyle에 reset을 적용시켜주는 방법에는 2가지가 있는데요
은수님처럼 직접 작성해도 너무 좋은것 같고 styled-reset 라이브러리를 사용하는 방법도 있어요😆

styled-reset 사용하기
styled-reset 라이브러리

display: none;
`;

export default function TodoCard({ isdone, todo, deleteTodo, toggleTodo }) {
Copy link

Choose a reason for hiding this comment

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

props로 넘겨받을때 구조분해할당하신거 너무 좋아요! 저의 경우에는 2개 이상의 props인 경우 가독성을 위해 아래와 같이 쓰는데요, 기능은 같으니까 참고만하시면 될 것 같아요 ㅎㅎ

Suggested change
export default function TodoCard({ isdone, todo, deleteTodo, toggleTodo }) {
export default function TodoCard(props) {
const {isdone, todo, deleteTodo, toggleTodo} = props;

Copy link

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.

정말 좋습니다!!

Copy link

@Rose-my Rose-my left a comment

Choose a reason for hiding this comment

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

글자를 클릭해도 todo <=> done이 되고 너무 좋은데요 ? 😆
컴포넌트 분리도 잘하시고 요렇게도 쓸 수 있구나 생각하게 되는 코드도 많았어요 > < 수고 하셨습니당 ㅎㅎ

Comment on lines +31 to +41
{todoList.map((todo, index) => {
return (
<TodoCard
key={index}
todo={todo.todo}
isdone={todo.isdone}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
);
})}
Copy link

Choose a reason for hiding this comment

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

map 함수 index까지 사용해서 너무 활용 잘하셨네요 😆

이렇게 배열 데이터를 순회하면서 컴포넌트에 mapping해주는 일을 앞으로 정말 많이 하게 될 텐데, 대부분의 경우 이번 처럼 다른 로직을 필요로 하지 않고 매핑할 컴포넌트만 반환해준답니당

이럴 땐 중괄호와 return문을 생략하고 바로 data.map((item,idx)=> <Btn>...</Btn>) 이렇게 표현해주면 간결해지는데요, return 값만 있을 경우 return 문을 생략할 수 있는건 ES6의 화살표 함수 개념이니까 참고하세요 > <

📚 MDN 문서

Suggested change
{todoList.map((todo, index) => {
return (
<TodoCard
key={index}
todo={todo.todo}
isdone={todo.isdone}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
);
})}
{todoList.map((todo, index) => (
<TodoCard
key={index}
todo={todo.todo}
isdone={todo.isdone}
deleteTodo={deleteTodo}
toggleTodo={toggleTodo}
/>
))}

}
}}
/>
<AddButton onClick={addButtonHandler}>+</AddButton>
Copy link

Choose a reason for hiding this comment

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

button 태그를 사용할 때는 type="button"을 명시하면 좋아요! 😁

button의 기본 타입은 "button"이 아닌 "submit"이기 때문인데요! submit의 경우 폼을 제출하는 이벤트를 발생시켜서 따로 설정을 하지 않는 이상 새로고침에 따라 사용자가 입력한 값을 저장하지 않고 날려버린다고 하네요..🥹

그래서 type="button"을 명시해주어 이 버튼은 그자체로 아무런 이벤트가 없음을 알리고 자바스크립트 click이벤트 등과 연결을 하면 돼요! > <

Copy link

Choose a reason for hiding this comment

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

Styled component를 만들어서 사용할 때도 이러한 속성들은 잊지않고 챙겨주는 것이 좋은데요 > <
대표적으로

button -> type="button"
img -> alt="대체 텍스트" 이런 친구들이 있어요 ㅎㅎ

}
}}
/>
<AddButton onClick={addButtonHandler}>+</AddButton>
Copy link

Choose a reason for hiding this comment

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

Suggested change
<AddButton onClick={addButtonHandler}>+</AddButton>
<AddButton type="button" onClick={addButtonHandler}>+</AddButton>

Comment on lines +49 to +51
} else if (todoList.some((item) => item.todo === inputValue)) {
alert('이미 등록된 할 일입니다.');
setInputValue('');
Copy link

@Rose-my Rose-my Mar 24, 2024

Choose a reason for hiding this comment

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

중복된 리스트 필터링까지...! 구현한거 보고 신기하다 생각했는데 some을 사용하신거였네요ㅎㅎ 많이 배워갑니당 😆


export default function DoneSection({ doneList, deleteTodo, toggleTodo }) {
return (
<DoneSectionLayout>
Copy link

Choose a reason for hiding this comment

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

여기뿐만아니라 모든 컴포넌트가 Layout으로 감싸져있네요! 너무 좋은데요? ㅎㅎ
Layout 네이밍도 잘하셔서 따로 styles폴더에 Layout.js을 만들어서 필요한 Layout들 import 해오는 것도 좋을 것 같네용 !!

Layout.js

export const DoneSectionLayout = styled.div`
  width: 100%;
  display: flex;
  flex-direction: column;
  flex: 1 1;
  overflow-y: scroll;
`;

export const TodoCardLayout = styled.div`
  width: 100%;
  height: 2.5rem;
  display: flex;
  align-items: center;
  padding: 0.5rem 1rem;
  gap: 0.5rem;
  &:hover {
    background-color: #f5f5f5;
    cursor: pointer;
    button {
      display: flex;
    }
  }
`;

Copy link

Choose a reason for hiding this comment

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

사용할 컴포넌트에는
import { DoneSectionLayout } from '@styles/Layout/DoneSectionLayout';
요렇게 불러와서 사용하면 되겠죠 ? 😆

Comment on lines +29 to +32
@media (max-width: 768px) {
height: 100%;
border-radius: 0;
}
Copy link

@Rose-my Rose-my Mar 24, 2024

Choose a reason for hiding this comment

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

미디어쿼리까지...! 미디어쿼리를 한 번도 안써봤는데 덕분에 감이 좀 잡히는것 같아요 ㅎㅎ

Comment on lines 12 to 18
let TodoContentBox = styled.div`
width: 100%;
display: flex;
flex-direction: column;
flex: 1 1;
overflow-y: scroll;
`;
Copy link

Choose a reason for hiding this comment

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

div 태그를 많이 쓰셨더라구요!

스타일컴포넌트를 쓸 때도 div보다는 시맨틱 태그를 적극적으로 활용해보시면 어떨까요?
여기서는 <article>태그를 사용해보세요! 이 태그는 독립적으로 배포 및 재사용할 수 있는 독립형 콘텐츠에 사용된답니당!😊

Copy link
Member

@leejin-rho leejin-rho left a comment

Choose a reason for hiding this comment

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

안녕하세요 프론트 운영진 노이진입니다!! 이번과제도 수고하셨습니다~!! 이번 과제 디자인 깔끔하게 신경쓰신거 좋습니다!! 다음과제는 디자인 협동 과제인 만큼 디자인 고민에서 해방될거에요 ㅎㅎㅎ 비대면 스터디에서 만나요~

<TodoListLayout>
<TodoHeader />
<TodoSection
todoList={todoList}
Copy link
Member

Choose a reason for hiding this comment

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

todo list가 많아지면 scroll이 생기면서 보기 안좋은데요, 이때 ::-webkit-scrollbar { display: none; }를 이용해서 스크롤 바를 없애보세요!~
스크린샷 2024-03-24 오후 5 51 23


let TodoCardLayout = styled.div`
width: 100%;
height: 2.5rem;
Copy link
Member

Choose a reason for hiding this comment

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

스크린샷 2024-03-24 오후 5 51 23 이렇게 글자가 넘치는 문제가 있어서, height를 정확히 지정하지 않고 fit-content;등을 통해 동적으로 변경시는건 어때요~!?

Copy link
Member

Choose a reason for hiding this comment

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

정말 좋습니다!!

const toggleTodo = (todo, isDone) => {
if (isDone) {
setDoneList((prev) => prev.filter((item) => item.todo !== todo));
setTodoList((prev) => [...prev, { todo, isdone: false }]);
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

@jinnyleeis jinnyleeis left a comment

Choose a reason for hiding this comment

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

단순히 밑줄로 done 상태의 할 일을 나타낸게 아니라,
todo 섹션과 done 섹션을 분리하신게 UI가 더 직관적인 것 같아 인상깊습니다!

또한 체크뿐만 아니라, 클릭만 해도, todo/done 상태를 변경할 수 있는 것도 정말 인상깊네요!! 정말 사용자 친화적인 유아이를 구현하신 것 같아 인상깊습니다!

많이 배우고 갑니다!

`;

export default function AddTodo() {
const { getTodoFromLocalStorage, setTodoToLocalStorage } = useLocalStorage();

Choose a reason for hiding this comment

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

저는 아직 커스텀 훅을 사용해보지 못했는데,
useLocalStorage() 관리하는걸 커스텀 훅으로 생성해서
하나의 훅에서 로컬 스토리지에 데이터 저장/ 불러오기 기능을 관리할 수 있어서 신기합니다!

display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;

Choose a reason for hiding this comment

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

저는 리엑트 기능 구현에 집중하느라 이번엔 절대단위로만 개발했는데
상대단위를 자유롭게 잘 활용하시는 것 같아 인상깊습니다!

max-width: 840px;
background-color: white;
@media (max-width: 768px) {

Choose a reason for hiding this comment

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

@media를 사용해 미디어 쿼리를 지정하신 것도 정말 인상 깊습니다!
진짜 사용자 친화적인 ui를 잘 구현하시는 것 같다는 생각이 다시 한 번 드네요!...

}
`;

function App() {
const [todoList, setTodoList] = useState([]);
const [doneList, setDoneList] = useState([]);
const { getTodoFromLocalStorage } = useLocalStorage();
const list = getTodoFromLocalStorage('todoList');

Choose a reason for hiding this comment

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

getTodoFromLocalStorage()가 컴포넌트 함수 바디의 최상단에서 호출되고 있어서,
컴포넌트가 리렌더링될 때마다 이 함수가 호출되는 것 같은데,
useEffect 내에서 한 번만 호출되도록 조정하는 것도 좋을 것 같다는 생각이 들어요!

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