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주차] 김다희 미션 제출합니다. #7

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

Conversation

Dahn12
Copy link

@Dahn12 Dahn12 commented Mar 22, 2024

🔗 배포 링크

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

🕶️ 기능구현

screenshot
  • todo 추가하기
  • local storage 구현
  • check버튼 클릭시 todo ↔ done으로 이동
  • delete버튼 클릭시 local storage에서 삭제
  • text overflow 관리
  • scroll 가능

👩‍💻 느낀점

지난 미션에서 local storage를 포함해서 여러가지 구현하지 못해 아쉬웠던 점을 많이 보완하고자 노력했습니다..! local storage로 데이터를 다루다 보니까 배열 형태를 가공하는 것이 많이 어려워서 헤매다 보니 시간이 부족해서 다른 분들의 코드를 많이 참고했습니다. 여전히 기능을 구현하는 것에 더 집중 해야겠다고 다짐했습니다. 리액트가 더 편할거라고 생각했는데 hook과 친해지지 못해서인지 오히려 더 어려웠던 미션이었습니다..🥹

👻 어려웠던점

  1. deleteTodo() 가 toggleTodo() 이후에 불려오면 에러가 남
    deleteTodo 함수가 filter를 통해 새롭게 배열을 반환해주기 때문에 이 함수가 먼저 오면 undefined가 뜸.
    따라서 useEffect를 통해 직접 제어해 줄 수 있으면 더 좋았을거라고 생각하지만, 정확한 구현은 하지 못해 생각을 더 해보아야 할 것 같다. 배열 자체를 todolist에 들어갈 것과 donelist에 들어갈것으로 나누어서 아예 분리된 배열을 만드는 것도 고려해 봤지만, 로컬스토리지 크기 자체가 작아서 효율적이지 않다고 생각함.
  2. deleteTodo함수에서 직접 배열 인덱스를 찾아서 slice하는 방법도 고민해보고 직접 구현해 보았지만, 인덱스로 찾게되면 매번 마운팅 될 때 마다 id 값이 일치하는 것을 찾고, 또 인덱스 값을 매번 계산하게 되어 여러번의 순차탐색을 하게되므로 효율성이 떨어진다고 생각함.
  3. 아직 props와 친해지지 못해서 ToDo 컴포넌트에 거의 모든 기능을 몰아넣고 자식 컴포넌트에 하나하나 props로 전달하는 방법만 썼으나, 좀더 효율적인 컴포넌트 구성을 고민하고 배치하여 한 컴포넌트에 하나의 기능이 들어갈 수 있게 재사용성이 좋은 컴포넌트를 만들어야겠다고 반성함.
  4. 막상 html에서 react로 넘어가니 익숙하지 않았다. useState 뿐 아니라 useEffect로도 관리 할 수 있었으면 훨씬 좋았을 것 같다.
  5. 사소한 부분이지만, 날짜를 가져오는 라이브러리도 많이 쓰이던 moment.js는 잘 안쓰이는 추세이고 훨씬 가벼운 days 라이브러리를 사용했다. 포맷 변환도 어려웠는데 days는 한국어도 지원하기 때문에 days github 를 참고해서 포맷하면 유용하다.

🔑 key questions

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

리액트에서는 virtual-DOM을 사용한다. virtual-DOM이란, 실제 DOM(Document Object Model)을 조작하는 방식이 아닌, 실제 DOM을 모방한 가상의 DOM을 구성해 원래 DOM과 비교하여 달라진 부분을 리렌더링 하는 방식으로 작동한다.
실제 DOM에는 브라우저가 화면을 그리는데 필요한 모든 정보가 들어있기 때문에 속도가 느리다는 단점이 있다. 리액트는 깜빡거림 없이 부드러운 UX를 사용자에게 제공하고자 변경사항만 빠르게 파악하고 리렌더링 하기 위해 DOM을 만들어 비교한다. 리액트는 성능 향상을 위해 실제 렌더링된 UI를 내부적으로 자바스크립트 객체로 따로 관리한다.

virtualDom

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

  1. virtual-DOM을 통해 리액트를 사용하지 않았을 때 보다 훨씬 빠른 렌더링이 가능했다.
  2. 기존에는 append를 통해 리스트를 관리했다면, 리액트는 list 하나를 컴포넌트로 빼두어서 더 쉽게 관리할 수 있었다.
  3. 기능별 또는 페이지 별로 컴포넌트로 관리해서 오류를 고칠 때 훨씬 쉽게 오류가 난 부분을 찾을 수 있었다.
  4. 라이브러리 적용이 쉽고 props로 빼두어 코드가 훨씬 간결해졌다.

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

컴포넌트 내부에서 변할수 있는 값이다. 상태는 React State로 다룰 수 있다.

state

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

가장 먼저 자주 쓰는 스타일을 props로 처리할 수 있다는 점이 가장 좋았다. 그 외에도 default 스타일을 적용하고 특정 컴포넌트에만 약간 변형된 스타일을 적용 할 수 있다는 것도 배웠는데, 아직 사용 해 보지는 못했다.

Copy link

@Programming-Seungwan Programming-Seungwan left a comment

Choose a reason for hiding this comment

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

다희님꼐서 깔끔하게 컴포넌트 구성 하신 것 너무 좋은 것 같습니다! 가장 외곽에 Todo컴포넌트를 App.jsx 파일에서 렌더링해주고 하위로 착착 내려가면서 직관적으로 웹 페이지 구성을 파악할 수 있어서 좋았던 것 같습니다.
useState() 말고도 다른 훅들도 적극적으로 사용하시면 더욱 성장하실 수 있을 것 같습니다. 좋은 코드 잘 봤습니다 :)

@@ -0,0 +1,12 @@
import Todos from "./components/Todos";

Choose a reason for hiding this comment

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

다희님 프로젝트 구조를 살펴보다가 본 것인데, index.js 파일이 PR된 것에는 생략되어 있는 것 같아요!
해당 코드를 보시면 createRoot() 메서드를 통해서 전체 이플리케이션을 html 문서 내부에 렌더링 하는데, git add .를 하실 떄 빼먹으신 것인지 확인 부탁 드려요 :)

import React, { useEffect } from 'react';
import Date from './Date';
import styled from 'styled-components';
import {css} from 'styled-components';

Choose a reason for hiding this comment

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

기존의 Scss나 Sass 같은 css 전처리기에서는 스타일링 코드를 일종의 "변수"처럼 활용할 수 있는데 styled-components 모듈의 css 속성을 활용하여 적용해 주셨군요! 저는 매번 styled만 이용했는데 잘 배우고 갑니다!
중복되는 로직은 함수로, React에서의 중복되는 상태 관심사는 커스텀 훅으로 관리하는 것처럼 css도 반복적으로 적용되는 컨셉은 styled-componentscss 를 통해서 할 수 있겠네요 :)


export default function Todos() {
//로컬스토리지 기존 값 가져오기
const savedLists = localStorage.getItem("lists")? JSON.parse(localStorage.getItem("lists")):[];

Choose a reason for hiding this comment

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

데이터를 따로 todo와 done으로 구분해주기보다, list key 값으로 한번에 관리해주셨네요! 삼항 연산자를 이용해서 실제 로컬 스토리지에 없는 경우에 대비해 주신 것도 좋습니다 👍

export default function Todos() {
//로컬스토리지 기존 값 가져오기
const savedLists = localStorage.getItem("lists")? JSON.parse(localStorage.getItem("lists")):[];
const [lists, setLists] = useState(savedLists);

Choose a reason for hiding this comment

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

아예 처음 Todo 컴포넌트가 dom에 마운트 될 때에 const [lists, setLists] = useState(null);로 초기화를 해두고, useEffect() 훅을 통해 그 안에서 로컬 스토리지에 접근하는 로직을 생각해보셔도 좋을 것 같아요

useEffect() 훅의 동작 시점을 이해하기 좋은 블로그 포스팅이 있어 함께 첨부할게요

Comment on lines +74 to +86
const deleteTodo = (id) => {
let deletedTodo = lists.filter((data) => data.id !== id);
setLists(deletedTodo);
localStorage.setItem("lists", JSON.stringify(deletedTodo));
};

const toggleTodo = (id) => {
let toggledTodo = lists.map((data) =>
data.id === id ? { ...data, completed: !data.completed } : data
);
setLists(toggledTodo);
localStorage.setItem("lists", JSON.stringify(toggledTodo));
};

Choose a reason for hiding this comment

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

todo를 삭제하는 로직과 toggle 해주는 로직을 구현하는, 내부적으로 다시 상태를 바꿔주는 핸들러 함수를 선언해주시고 자식 컴포넌트들에게 prop으로 넘겨주셨네요! 배열의 다양한 메서드를 잘 사용하시고 너무 좋은데, React 코드 컨벤션에서 해당 함수명을 handle 접두어를 붙이는 것이 컨벤션으로 알고 있어요. 예를 들어 handleDeleteTodo, handleToggleTodo와 같은 방식으로요!
사실 이것도 개발자의 취향 같은 것이라고 생각해서 너무 신경은 안 쓰셔두 될 것 같습니다

Choose a reason for hiding this comment

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

deletodo와 toggletodo에서 나누어서 상태 업데이트 로직을 관리해주셨는데
한 번의 상태 업데이트로 필요한 상태에 관한 모든 변경을 반영할 수 있도록 하면 말씀해주신 오류가 발생하지않지 않을까 싶어요!
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(lists));
}, [lists]);
이런식으로 useEffect 활용하는 방법을 다음에 구현해보시길 추천드려요!!

${button}
`

function ListsItems({todo, data, deleteTodo, toggleTodo}) {

Choose a reason for hiding this comment

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

지금 저희가 하는 todoList 앱 정도는 사실 상태의 개수가 그렇게 많지는 않기에 상위 컴포넌트에서 state를 선언하고 prop으로 꽂아주는 prop drilling 방식을 사용하는 것도 나쁘지 않아 보여요!
그런데 개인적으로는 매번 prop을 쓸 때 실수로 오타가 나고하는 것이 상당히 불편해서 저는 contextAPI를 사용했어요.

  1. createContext()로 일단 컨텍스트를 만들어 주기 관련 제 코드입니다
  2. 상태를 상위 컴포넌트에서 만들어주기 useState() 활용 관련 링크
  3. 사용의 범위의 가장 바깥에 있는 컴포넌트에서 provider를 이용해 감싸주기 관련 링크
  4. 그리고 원하는 곳에서 useContext(1에서 만들어준 컨텍스트 명) 로직을 이용해서 원하는 상태를 구조 분해하면 됩니다

`
const GetDate = styled.h1`
margin: 0;
font-size: 3rem;

Choose a reason for hiding this comment

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

코드 전반적으로 rem이라는 기준이 딱 잡혀있는 크기 단위를 적용해주신 것이, 여러 디바이스를 고려할 때 반응형 웹 디자인을 구축할 때 좋은 것 같습니다. 이렇게 적용하고 필요한 디바이스에서는 미디어 쿼리를 사용하여 예외 처리 느낌으로 코드를 구성하면 좋을 것 같네요 :)

}

return (
<TodoInputFieldWrapper onSubmit={onSubmit}>

Choose a reason for hiding this comment

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

form 태그를 이용하면 다희님께서 만들어주신 추가 버튼을 통한 방식, 사용자가 엔터 키를 누르는 방식 모두 정상적으로 작동한다는 점에서 좋은 것 같아요. 다만 TodoInputFieldWrapper 컴포넌트의 이름에서 form을 담았다는 것을 다른 개발자들도 알 수 있도록 네이밍 해주시면 더 좋을 것 같습니다 💪

"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"styled-components": "^6.1.8",
"web-vitals": "^2.1.4"
},
"scripts": {

Choose a reason for hiding this comment

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

react-scripts 라이브러리가 위의 dependencies에 설치되었고 우리가 npm run start 를 누르면 리액트 애플리케이션이 동작하는 의미에 대해서 한번 생각해보면 좋을 것 같아요! CRA가 아니라 vite 같은 방식으로 리액트 앱을 만들 때에는 react-scripts 모듈이 아니라 vite러너가 이를 실행시켜주는데, 둘의 차이와 퍼포먼스에 대해서 알아보셔도 좋을 것 같습니다 :)

import {css} from 'styled-components';
import TodoInputField from './Lists/TodoInputField';
import { useState } from 'react';
import ListsItems from './Lists/ListsItems';

Choose a reason for hiding this comment

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

매번 다른 컴포넌트들을 import 해올 때, 상대 경로로 적어주는 것이 약간 가독성을 해친다는 생각이 들었어요(근데 저도 이렇게 과제 했습니다....🤣). 유담님 코드 구경하는데 jsconfig.json 파일에서 절대 경로를 설정해주는 것을 보고 리팩터링 해보시면 좋을 것 같습니다.
유담님 코드 레퍼런스

Copy link

@geeoneee geeoneee left a comment

Choose a reason for hiding this comment

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

다희님 안녕하세요! 프론트엔드 운영진 김지원입니다 😀
깔끔한 UI와 코드가 돋보이는 과제였던 것 같습니다~
이번에도 과제 하느라 수고 많으셨어요! 다음 과제도 파이팅~~👍🏻

@@ -0,0 +1,47 @@
import React from 'react'

Choose a reason for hiding this comment

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

리액트17부터는 더이상 모든 파일에서 React를 import하지 않아도 된다고 합니다!
성능 관리 측면에서 불필요한 Import는 줄이는게 좋겠죠??
참고하시라고 관련 문서 하나 같이 붙여드려요😀
import React from 'react' 생략

@@ -0,0 +1,47 @@
import React from 'react'
import styled from 'styled-components'
import dayjs from 'dayjs'

Choose a reason for hiding this comment

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

날짜 구현에dayjs 라이브러리를 사용하셨군요!!
저는 아직 사용해본 적 없는 라이브러리라 관련 내용 찾아봤어요~ 필요하신 분들 참고하시라고 링크 남겨드려요
dayjs 라이브러리

//객체 생성
function getList(todo) {
const newList = {
id: dayjs(),

Choose a reason for hiding this comment

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

dayjs 이용해서 아이디 부여해주신 점 너무 좋아요~

title: todo,
completed: false
}
setLists([...lists, newList]);

Choose a reason for hiding this comment

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

스프레드 연산자를 잘 사용하시는 것 같아요~~

import Todos from "./components/Todos";


function App() {

Choose a reason for hiding this comment

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

폰트를 적용 안하신것같은데,
app 컴포넌트 파일에서
여기서 글로벌 스타일 속성으로 폰트도 적용해보시는걸 추천드려봐요!

Choose a reason for hiding this comment

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

check버튼 클릭시 todo ↔ done으로 이동하는걸
그냥 취소선이 아니라, 직접 이미지를 사용하셔서 체크 버튼이 채워지는 것으로 표현하신게 화면으로 보기에도 좋아 좋은 아이디어인 것 같습니다!

Comment on lines +74 to +86
const deleteTodo = (id) => {
let deletedTodo = lists.filter((data) => data.id !== id);
setLists(deletedTodo);
localStorage.setItem("lists", JSON.stringify(deletedTodo));
};

const toggleTodo = (id) => {
let toggledTodo = lists.map((data) =>
data.id === id ? { ...data, completed: !data.completed } : data
);
setLists(toggledTodo);
localStorage.setItem("lists", JSON.stringify(toggledTodo));
};

Choose a reason for hiding this comment

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

deletodo와 toggletodo에서 나누어서 상태 업데이트 로직을 관리해주셨는데
한 번의 상태 업데이트로 필요한 상태에 관한 모든 변경을 반영할 수 있도록 하면 말씀해주신 오류가 발생하지않지 않을까 싶어요!
useEffect(() => {
localStorage.setItem("lists", JSON.stringify(lists));
}, [lists]);
이런식으로 useEffect 활용하는 방법을 다음에 구현해보시길 추천드려요!!

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.

다희님 todo item/ done item이 많아져도,
레이아웃이 흐트러지지 않고, todo 목록이 스크롤되도록 구현하신게 정말 인상깊었어요!!
아까 세션에서 이야기했던 것처럼 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