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

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

Conversation

ZUITOPIA
Copy link

@ZUITOPIA ZUITOPIA commented Sep 3, 2024

안녕하세요, LG U+ URECA 프론트엔드 대면반 1기 고주희입니다.

이번 미션은 지난 번과 같은 기능을 바닐라 자바스크립트가 아닌 리액트로 구현해보는 것이었습니다. 리액트를 사용하여 프로젝트를 진행할 때마다 매번 세팅은 똑같이 하고 프로젝트의 기획만 다르게 하였었는데, 이번 미션을 통해 다양한 것들을 추가로 공부해볼 수 있는 기회가 되어 무척 의미있었습니다!

중점적으로 고려한 요소

  • 현 프로젝트는 기본 todo 구현이었으므로 input 창을 재사용 할 일이 많지 않았지만, 지금까지의 프로젝트 경험들을 되돌아 보았을 때 input 창은 재사용할 일이 많았던 것으로 기억합니다. 그리하여 이번 기회에 커스텀 훅을 공부하고자 useInput이라는 hook을 생성해보았습니다. 또한, state를 따로 상태관리 라이브러리를 사용하여 관리하지 않고 localStorage에 저장하는 것이 필수 과제였기에 localStorage에 접근하는 코드가 많았습니다. 그래서 따로 useLocalStorage 라는 hook으로 빼주었습니다.
  • 리액트에서 기능 단위로 컴포넌트를 나누는 만큼 각각의 폴더를 기능별로 구분하여 사용하기 위해 노력했습니다.
  • Text와 같은 자주 사용해야 할 style은 UI 파일을 생성하고 import 하여 사용하도록 하였습니다. 이 후 프로젝트에서는 Text나 checkbox 뿐만 아니라 기본 Wrapper로 사용할 box 등 재사용이 높은 style들을 한 파일에서 관리할 예정입니다!

알게 된 부분

  • 지금껏 패키지 매니저는 npm 만 사용해보았었는데 이번 기회에 yarn을 사용해보게 되었습니다. 프로젝트의 범위가 크지는 않아서 기존 npm을 사용할 때에 비하여 직접적인 속도 향상을 느껴보지는 못 하였지만 좋은 경험이 되었으며 앞으로도 자주 사용할 것 같습니다!
  • styled-components 사용 경험이 있는 상태에서 emotion을 사용하게 되었는데 무척 익숙해서 찾아보니 실제로 제공하는 기능과 성능이 거의 유사했습니다. 아마 emotion을 styled-components 처럼 사용하기 쉽도록 만들어진 라이브러리이기 때문에 어쩌면 당연한 것 같기도 합니다. emotion을 사용하는 만큼 css를 props로 넘겨 사용하는 것에도 익숙해지도록 연습이 조금 더 필요할 것 같습니다.
  • 매번 CRA로만 리액트 환경을 구성하고 프로젝트를 생성했었기 때문에 Vite라는 것 역시 처음 알게되었습니다. CRA를 사용할 때보다 서버 실행 속도나 빌드 속도가 빨라지는 등의 장점이 있기 때문이라는 것을 공부할 수 있었고, 이 역시도 추후에 조금 더 무게감있는 프로젝트에서 느껴볼 수 있을 것 같다는 생각이 들었습니다.

리팩토링 예정 리스트

  • 동작에 효과 넣어보기
  • 디자인 수정 ✅
  • 글꼴 적용하기 ✅
  • 배포 ✅
  • 파일 기능 단위로 조금 더 쪼개기 ✅
  • 스타일 파일 분리 ✅
  • @emotion/css 사용에도 익숙해지기
  • 모바일로 배포 링크 접속 시 todo 추가 버튼 깨짐 현상 해결하기
  • 파비콘 변경 ✅

생각해 볼 질문들

  1. 지난주 미션과 비교했을 때, React로 구현하는 과정에서 어떤 점이 더 편리하거나 복잡했나요?
  • 직접 태그 내부에 접근하지 않고 jsx를 사용하기 때문에 직접적으로 DOM 요소 하나하나에 접근하지 않고도 빠르게 화면 구성을 하고, 데이터를 동적으로 보여줄 수 있다는 점에서 훨씬 편리했습니다.
  • 파일을 기능 단위로 쪼개는 만큼 props를 전달할 일이 많은데, 기존 파일을 수정할 때 관련된 부분이 많아 파일의 흐름(부모와 자식 관계)을 계속해서 찾아야 하는 부분이 수월하지는 않았던 것 같습니다.
  1. React의 Virtual DOM(가상 돔)이란 무엇이고, 이를 사용했을 때의 장점은 무엇이 있나요?
  • 명확한 답이 어려워 찾아보았습니다.

Virtual DOM의 동작 방식:

  1. 초기 렌더링: React 컴포넌트가 처음 렌더링될 때, 실제 DOM에 요소를 그리는 대신 Virtual DOM이라는 가상 표현을 생성함. (이 Virtual DOM은 메모리 상에 존재하는 자바스크립트 객체임)

  2. 상태 변경 감지: 사용자가 인터랙션을 하거나 상태(state)가 변경되면, React는 새로운 Virtual DOM을 생성함. 이 Virtual DOM은 변경된 상태를 반영하고, 이전의 Virtual DOM과 비교를 통해 어떤 부분이 실제 DOM에서 변경되어야 하는지 찾음.

  3. 디프(Diffing): React는 이전 Virtual DOM과 새로운 Virtual DOM을 비교하여 변경된 부분을 찾음. ( = 디프 알고리즘(diffing algorithm) ) 이 알고리즘은 효율적으로 변경된 요소만을 추적하여 최소한의 연산으로 업데이트를 진행할 수 있게 함.

  4. 리콘실리에이션(Reconciliation): 변경된 부분을 실제 DOM에 적용하는 과정을 말함. React는 변경된 요소만 실제 DOM에 반영하여, 불필요한 전체 재렌더링을 피하고 성능을 향상시킴.

Virtual DOM의 장점:

  • 성능 최적화: 실제 DOM 조작은 느리기 때문에, Virtual DOM을 사용하여 변경된 부분만 업데이트하면 성능이 크게 향상됨.
  • 추상화: 개발자가 DOM 조작을 직접 관리할 필요 없이, React가 알아서 효율적인 방법으로 UI를 업데이트해줌.
  • 예측 가능성: 상태와 UI의 관계를 명확하게 관리할 수 있어, 코드의 예측 가능성과 유지 보수성이 높아짐.
  1. React에서 state와 props는 무엇이고 어떻게 사용하나요?
  • state는 동적으로 변경되는 데이터로 상태가 변경됨에 따라 컴포넌트의 리렌더링이 가능하도록 하게 하는 것으로 알고 있습니다. 보통 useState을 선언하여 초기화합니다.
  • props는 부모에서 자식으로 넘겨주는 데이터를 칭하는 말로 알고 있습니다. 보통 HTML 태그 내부에 속성을 정의하듯 넘겨줄 props를 지정하곤 합니다.
  1. React에서 컴포넌트를 분리하는 기준은 무엇일까요?
  • 재사용되는 일이 많은 기능을 따로 빼주는 것으로 알고있습니다.

배포 링크

https://zuitopia-todo-with-react.vercel.app/

결과 화면

image

ZUITOPIA and others added 22 commits September 2, 2024 17:20
fix: 컴포넌트 이름 import 시 소문자를 대문자로
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.

안녕하세요 주희 님!
이번주 과제도 고생 많으셨습니다 🙂👍🏻

"singleQuote": true
"singleQuote": false
Copy link
Member

Choose a reason for hiding this comment

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

프리티어 설정 확인해 주세요!

index.html Outdated
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="stylesheet" href="./reset.css" />
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 작성하는 것보다는 전역적으로 스타일을 적용하고 싶다면 Emotion의 Global 컴포넌트를 이용해 보세요 🙂👍🏻
Emotion - Global Styles

src/App.jsx Outdated
import styled from "@emotion/styled";
import { Text } from "./components/shared/UI";

const Style = {
Copy link
Member

Choose a reason for hiding this comment

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

(개인적인 생각입니다)
스타일 코드보다 컴포넌트 코드의 중요성이 더 높다 보니 스타일 코드는 컴포넌트 코드 아래에 위치시키는 것은 어떨까요?!

};

export default function App() {
const [todos, setTodos] = useLocalStorage("todos", []);
Copy link
Member

Choose a reason for hiding this comment

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

오호 로컬스토리지 관련 로직을 커스텀훅으로 만드셨군요 👍🏻👍🏻

src/App.jsx Outdated
Comment on lines 63 to 85
<Style.Header>
<Text.Title>SCHEDULE</Text.Title>
<Input addTodo={handleAddTodo} />
</Style.Header>
<Style.TaskWrapper>
<Text.MiniTitle>{todos.length} tasks</Text.MiniTitle>
<Content
items={todos}
onToggle={handleToggleTodo}
onDelete={deleteTodo}
isCompleted={false}
/>
</Style.TaskWrapper>

<Style.TaskWrapper>
<Text.MiniTitle>{completed.length} tasks</Text.MiniTitle>
<Content
items={completed}
onToggle={handleToggleTodo}
onDelete={deleteDone}
isCompleted={true}
/>
</Style.TaskWrapper>
Copy link
Member

Choose a reason for hiding this comment

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

<Header><TaskWrapper> 부분을 컴포넌트로 분리해도 좋을 것 같습니다!

Comment on lines 54 to 66
{isCompleted ? (
<>
<Style.CheckedBox
onClick={() => onToggle(index, isCompleted)}
/>
</>
) : (
<>
<Style.UnCheckedBox
onClick={() => onToggle(index, isCompleted)}
/>
</>
)}
Copy link
Member

Choose a reason for hiding this comment

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

각 항에 대해 하나의 요소만 존재하므로 Fragment는 없어도 될 것 같습니다!

Suggested change
{isCompleted ? (
<>
<Style.CheckedBox
onClick={() => onToggle(index, isCompleted)}
/>
</>
) : (
<>
<Style.UnCheckedBox
onClick={() => onToggle(index, isCompleted)}
/>
</>
)}
{isCompleted ? (
<Style.CheckedBox onClick={() => onToggle(index, isCompleted)} />
) : (
<Style.UnCheckedBox
onClick={() => onToggle(index, isCompleted)}
/>
)}

@@ -10,6 +10,9 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"prop-types": "^15.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Copy link
Member

Choose a reason for hiding this comment

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

저희는 yarn을 사용하기 때문에 package-lock.json 파일은 지우셔도 될 것 같습니다 🙂
참고 자료 - 패키지 잠금 파일 (package-lock.json, yarn.lock)

src/App.jsx Outdated
const [completed, setCompleted] = useLocalStorage("completed", []);

const handleAddTodo = (todo) => {
setTodos([...todos, todo]);
Copy link
Member

@corinthionia corinthionia Sep 4, 2024

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
setTodos([...todos, todo]);
setTodos((prev) => [...prev, todo]);

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

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

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

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

export default function Input({ addTodo }) {
const [inputValue, handleChange, setInputValue] = useInput("");

const handleAddTodo = (event) => {
Copy link
Member

Choose a reason for hiding this comment

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

이벤트 핸들러의 이름은 handle + 대상 + 동작 의 형식으로 짓습니다!
이 경우, <form>에 바인딩 된 onSubmit 핸들러이기 때문에 아래와같이 작성할 수 있습니다.

Suggested change
const handleAddTodo = (event) => {
const handleFormSubmit = (event) => {

참고자료 - [번역] React의 이벤트 핸들러 네이밍 (Event handler naming in react)

Comment on lines 7 to 9
{items.map((todo, index) => (
<TaskItem
key={index}
Copy link
Member

@corinthionia corinthionia Sep 4, 2024

Choose a reason for hiding this comment

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

key 값으로 map()index를 사용하는 방법은 바람직하지 않습니다!

리스트 항목의 순서가 바뀌거나 항목이 추가/삭제될 때, 각 항목에 대응되는 index 값이 변경되기 때문에 이전 key와 새로운 key가 달라지게 됩니다. 이는 곧 React가 해당 항목을 새롭게 렌더링해야 한다고 판단하여 불필요한 리렌더링을 초래할 수 있습니다.

즉, key를 사용하는 이유가 불필요한 리렌더링의 방지인데, 이 경우 오히려 불필요한 리렌더링이 발생할 수 있다는 것입니다!

스크린샷 2024-09-04 오전 12 53 53

참고자료 - React 공식문서 key 규칙
참고자료 - 리액트에서 key에 index를 넣으면 안 되는 '진짜' 이유

Copy link

@jissssu jissssu left a comment

Choose a reason for hiding this comment

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

주희님 미션 수행하느라 수고 많으셨습니다!!!
주희님 코드 보면서 컨포넌트 구성에 있어 어떤식으로 재사용 가능하게 설계해야할지, 로컬스토리지 값을 관리하는 hook에 대한 부분도 정말 많이 배울 수 있었습니다!
디자인도 너무 이뻐요🥹 다음번 미션도 같이 화이팅합시다!

src/App.jsx Outdated
Comment on lines 2 to 5
import useLocalStorage from "./components/hooks/useLocalStorage";
import Input from "./components/shared/Input";
import { Text } from "./components/shared/UI";
import { Style } from "./components/styles/App.styles";
Copy link

Choose a reason for hiding this comment

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

useLocalStorage와 같은 커스텀 훅을 사용하여 로컬 스토리지 관련 기능을 처리한 부분! 저도 배워갑니다!

src/App.jsx Outdated
Comment on lines 29 to 37
const deleteTodo = (index) => {
const newTodos = todos.filter((_, i) => i !== index);
setTodos(newTodos);
};

export default App;
const deleteDone = (index) => {
const newCompleted = completed.filter((_, i) => i !== index);
setCompleted(newCompleted);
};
Copy link

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 +14
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});

useEffect(() => {
localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);

return [storedValue, setStoredValue];
}
Copy link

Choose a reason for hiding this comment

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

저도 이런식으로 localstorage를 따로 뺴서 적용해봐야겠네요! 덕부에 배워갑니다!

Comment on lines +1 to +5
import useInput from "../hooks/useInput";
import { Style } from "../styles/Input.styles";

export default function Input({ addTodo }) {
const [inputValue, handleChange, setInputValue] = useInput("");
Copy link

Choose a reason for hiding this comment

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

useInput 훅을 활용해 입력 값의 상태를 관리하는 부분이 매우 깔끔한 것 같습니다!!

export default function Task({ items, onToggle, onDelete, isCompleted }) {
return (
<Style.Wrapper>
{items.map((todo, index) => (
Copy link

Choose a reason for hiding this comment

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

저도 이 부분에 있어서 예전에 조언받았는데! index를 키로 사용할 경우 항목의 순서가 바뀔 때 리렌더링 최적화에 문제가 생길 수 있기에! todo 항목의 고유 식별자(예: id)를 key로 사용하는 것이 좋다고합니다!

</>
) : (
<>
<Button.UnCheckedBox isChecked={isCompleted} onClick={onToggle} />
Copy link

Choose a reason for hiding this comment

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

조건부 렌더링 덕분에 가독성이 더좋은 것 같아요! 저도 다음에 이런식으로 코드 작성해야겠어요!

Comment on lines +59 to +60
opacity: 0.5;
filter: invert();
Copy link

Choose a reason for hiding this comment

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

오홍 filter는 사용해본적 없는데 덕분에 또 배워갑니다!

Copy link
Member

@Yunil-dev Yunil-dev left a comment

Choose a reason for hiding this comment

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

주희님의 코드리뷰를 하게 되어서 너무 좋습니다. 제가 더 얻어가는 게 많네요 !
로직 구현과 폴더 구조등을 보면 고민을 많이 하신 게 티가 나네요! 저도 얼릉 성장해서 주희님과 행복한 플젝 하고 싶습니당🥰

public/emoji.png Outdated
Copy link
Member

Choose a reason for hiding this comment

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

불필요한 이미지는 삭제해주세요 !


const handleToggleTodo = (index, isCompleted) => {
if (isCompleted) {
const newCompleted = completed.filter((_, i) => i !== index);
Copy link
Member

Choose a reason for hiding this comment

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

이벤트 핸들러 네이밍 잘 지켜주셨군요 ! 저도 잘 지켜야 할 텐데 말입니다..

src/App.jsx Outdated
};

return (
<Style.AppWrapper>
Copy link
Member

Choose a reason for hiding this comment

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

emotion에서의 style()를 적용 잘하신 거 같아요! 저는 css()만 사용해서 입혔는데 다음에 시도해봐야겠습니다 +-+ !!

}, [key, storedValue]);

return [storedValue, setStoredValue];
}
Copy link
Member

Choose a reason for hiding this comment

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

저는 아래 코드처럼 로컬스토리지를 구현했습니다. 근데 주희님은 커스텀훅으로 따로 만드셨더라구요! 따로 만들게 된 이유와 따로 만들게 되면 이점이 무엇인지 궁금합니다.

 // 처음 컴포넌트 렌더링될 때 로컬 스토리지에서 할 일 목록 불러오기
  const [todoList, setTodoList] = useState(() => {
    const savedTodoList = localStorage.getItem('todoList');
    return savedTodoList ? JSON.parse(savedTodoList) : [];
  });

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

Comment on lines +59 to +60
opacity: 0.5;
filter: invert();
Copy link
Member

Choose a reason for hiding this comment

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

filter, opacity 는 처음보네요! 잘 알아갑니다☺️

  • filter : 흐림 효과나 색상 변형 등 그래픽 효과를 요소에 적용한다.
  • opacity : 불투명도 설정, 요소 뒤에 있는 콘텐츠가 숨겨지는 정도.

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